diff --git a/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt b/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt index f9f31ea0c7f7cdf2d5226f7302e1f84d682bdb1e..ebb05e61457450b777ae9211dd6b782a1711777d 100644 --- a/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt @@ -219,7 +219,7 @@ class JamiAccountSummaryFragment : mBinding?.appBar?.setLiftOnScrollTargetView(v) } - override fun onToolbarTitleChanged(title: String) { + override fun onToolbarTitleChanged(title: CharSequence) { mBinding?.toolbar?.title = title } //=============== AppBar management end =================== diff --git a/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt b/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..d29c7ddbadb7458e52b0677841f4b0709b008be1 --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/fragments/ConnectionMonitorFragment.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2004-2024 Savoir-faire Linux Inc. + * + * 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 <https://www.gnu.org/licenses/>. + */ +package cx.ring.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import cx.ring.R +import cx.ring.databinding.FragConnectionMonitorBinding +import cx.ring.databinding.ItemDeviceConnectionBinding +import cx.ring.databinding.ItemListContactBinding +import cx.ring.interfaces.AppBarStateListener +import cx.ring.views.AvatarDrawable +import dagger.hilt.android.AndroidEntryPoint +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.ContactViewModel +import net.jami.services.AccountService +import net.jami.services.AccountService.ConnectionStatus +import net.jami.services.ContactService +import javax.inject.Inject + +@AndroidEntryPoint +class ConnectionMonitorFragment: Fragment() { + + @Inject + lateinit var service: AccountService + @Inject + lateinit var contactService: ContactService + + private var list: RecyclerView? = null + private val disposableBag = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragConnectionMonitorBinding.inflate(inflater, container, false).apply { + root.adapter = ConnectionAdapter() + list = root + }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + (parentFragment as? AppBarStateListener)?.apply { + onToolbarTitleChanged(getText(R.string.pref_connection_monitor)) + onAppBarScrollTargetViewChanged(list) + } + } + + data class DeviceConnectionViewModel( + val contact: ContactViewModel?, + val connection: AccountService.DeviceConnection? + ) + + class ConnectionAdapter(var connections: List<DeviceConnectionViewModel> = emptyList()): RecyclerView.Adapter<ConnectionAdapter.ConnectionViewHolder>() { + class ConnectionViewHolder(val binding: ItemListContactBinding?, val connBinding: ItemDeviceConnectionBinding?): RecyclerView.ViewHolder(binding?.root ?: connBinding!!.root) + + override fun getItemViewType(position: Int): Int = + if (connections[position].contact != null) 0 else 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConnectionViewHolder = + if (viewType == 0) + ConnectionViewHolder(ItemListContactBinding.inflate(LayoutInflater.from(parent.context), parent, false), null) + else + ConnectionViewHolder(null, ItemDeviceConnectionBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + + override fun onBindViewHolder(holder: ConnectionViewHolder, position: Int) { + holder.connBinding?.let { + val connection = connections[position].connection!! + it.name.text = connection.device + it.device.text = connection.remoteAddress + it.device.isVisible = !connection.remoteAddress.isNullOrEmpty() + it.icon.setImageResource(when (connection.status) { + ConnectionStatus.Waiting -> 0 + ConnectionStatus.Connecting -> R.drawable.baseline_radar_24 + ConnectionStatus.ICE -> R.drawable.p2p_24 + ConnectionStatus.TLS -> R.drawable.baseline_private_connectivity_24 + ConnectionStatus.Connected -> R.drawable.baseline_private_connectivity_24 + }) + it.icon.imageTintList = when (connection.status) { + ConnectionStatus.Connected -> ContextCompat.getColorStateList(it.root.context, R.color.green_500) + else -> ContextCompat.getColorStateList(it.root.context, R.color.icon_color) + } + it.icon.contentDescription = connection.status.toString() + } + holder.binding?.let { + val contact = connections[position].contact!! + it.photo.setAvatar(AvatarDrawable.Builder() + .withContact(contact) + .withCircleCrop(true) + .build(it.root.context)) + it.convParticipant.text = contact.displayName + it.convLastItem.text = contact.displayUri + } + } + + fun setData(newConnections: List<DeviceConnectionViewModel>) { + val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int = connections.size + override fun getNewListSize(): Int = newConnections.size + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = connections[oldItemPosition] + val newItem = newConnections[newItemPosition] + return if (oldItem.contact != null && newItem.contact != null) { + oldItem.contact.contact.uri == newItem.contact.contact.uri + } else { + oldItem.connection?.id == newItem.connection?.id + } + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = connections[oldItemPosition] + val newItem = newConnections[newItemPosition] + return if (oldItem.contact != null && newItem.contact != null) { + oldItem.contact.contact.uri == newItem.contact.contact.uri + && oldItem.contact.displayName == newItem.contact.displayName + } else { + oldItem.connection?.id == newItem.connection?.id + && oldItem.connection?.status == newItem.connection?.status + } + } + + }) + connections = newConnections + diff.dispatchUpdatesTo(this) + } + override fun getItemCount(): Int = connections.size + } + + override fun onStart() { + super.onStart() + disposableBag.add((service.monitorConnections() + .switchMapSingle { (accountId, connections) -> + if (connections.isEmpty()) + Single.just(emptyList()) + else + Single.zip(connections + .map { (peer, connections) -> + contactService.getLoadedContact(accountId, peer).map { Pair(it, connections) } + }) { it.map { it as Pair<ContactViewModel, List<AccountService.DeviceConnection>> } } + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + val adapter = list?.adapter as? ConnectionAdapter + adapter?.setData(it.map { (contact, connections) -> + val list = ArrayList<DeviceConnectionViewModel>(1 + connections.size) + list.add(DeviceConnectionViewModel(contact, null)) + connections.forEach { connection -> + list.add(DeviceConnectionViewModel(null, connection)) + } + list + }.flatten()) + })) + } + + override fun onStop() { + super.onStop() + disposableBag.clear() + } + +} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt b/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt index 803e516e2d36e2b52f0ae9f1b05e8358b1914b84..eea451654d0c02cd64cd049b47386ab9524f4722 100644 --- a/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt +++ b/jami-android/app/src/main/java/cx/ring/interfaces/AppBarStateListener.kt @@ -4,6 +4,6 @@ import android.view.View /** An interface to be implemented by Activities or Fragments that host {@link com.google.android.material.appbar.AppBarLayout} */ interface AppBarStateListener { - fun onToolbarTitleChanged(title: String) + fun onToolbarTitleChanged(title: CharSequence) fun onAppBarScrollTargetViewChanged(v: View?) } \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt b/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt index 483047a1c6c58206379c4c72d758738fd0506868..cc6b32a729aad5c31a177315fce00b650262dd4f 100644 --- a/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/settings/SettingsFragment.kt @@ -34,6 +34,7 @@ import cx.ring.R import cx.ring.application.JamiApplication import cx.ring.client.LogsActivity import cx.ring.databinding.FragSettingsBinding +import cx.ring.fragments.ConnectionMonitorFragment import cx.ring.interfaces.AppBarStateListener import cx.ring.mvp.BaseSupportFragment import cx.ring.settings.extensionssettings.ExtensionDetails @@ -49,8 +50,6 @@ import net.jami.mvp.GenericView import net.jami.settings.SettingsPresenter import net.jami.settings.SettingsViewModel import net.jami.utils.DonationUtils -import net.jami.utils.DonationUtils.endDonationTimeMillis -import net.jami.utils.DonationUtils.startDonationTimeMillis @AndroidEntryPoint class SettingsFragment : @@ -130,6 +129,17 @@ class SettingsFragment : settingsLogs.setOnClickListener { v: View -> startActivity(Intent(v.context, LogsActivity::class.java)) } + connectionMonitor.setOnClickListener { v: View -> + val content = ConnectionMonitorFragment() + childFragmentManager + .beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.fragment_container, content, VIDEO_SETTINGS_TAG) + .addToBackStack(VIDEO_SETTINGS_TAG).commit() + fragmentContainer.isVisible = true + donateButton.isVisible = false + backPressedCallback.isEnabled = true + } toolbar.setNavigationOnClickListener { activity?.onBackPressedDispatcher?.onBackPressed() } @@ -293,7 +303,7 @@ class SettingsFragment : binding?.appBar?.setLiftOnScrollTargetView(v) } - override fun onToolbarTitleChanged(title: String) { + override fun onToolbarTitleChanged(title: CharSequence) { binding?.toolbar?.title = title } //=============== AppBar management end =================== diff --git a/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml b/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..2da477d0d4fab27154cf575cee20f1b8de7d9681 --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_private_connectivity_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,760Q374,760 295.5,691.5Q217,623 203,520L80,520L80,440L203,440Q217,337 295.5,268.5Q374,200 480,200Q586,200 664.5,268.5Q743,337 757,440L880,440L880,520L757,520Q743,623 664.5,691.5Q586,760 480,760ZM400,620L560,620Q577,620 588.5,608.5Q600,597 600,580L600,460Q600,443 588.5,431.5Q577,420 560,420L560,420L560,384Q560,349 537,324.5Q514,300 480,300Q447,300 423.5,323.5Q400,347 400,380L400,420L400,420Q383,420 371.5,431.5Q360,443 360,460L360,580Q360,597 371.5,608.5Q383,620 400,620ZM480,550Q467,550 458.5,541.5Q450,533 450,520Q450,507 458.5,498.5Q467,490 480,490Q493,490 501.5,498.5Q510,507 510,520Q510,533 501.5,541.5Q493,550 480,550ZM440,420L440,380Q440,363 451.5,351.5Q463,340 480,340Q497,340 508.5,351.5Q520,363 520,380L520,420L440,420Z"/> +</vector> diff --git a/jami-android/app/src/main/res/drawable/baseline_radar_24.xml b/jami-android/app/src/main/res/drawable/baseline_radar_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..a8ed1344ed6feb283f0317a3de647f88ca3bc8e6 --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_radar_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q536,800 585.5,782.5Q635,765 676,733L619,676Q590,697 554.5,708.5Q519,720 480,720Q380,720 310,650Q240,580 240,480Q240,380 310,310Q380,240 480,240Q580,240 650,310Q720,380 720,480Q720,519 708,555Q696,591 675,620L732,677Q764,636 782,586Q800,536 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,640Q502,640 522.5,634.5Q543,629 561,618L500,557Q495,559 490,559.5Q485,560 480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,486 559.5,491.5Q559,497 557,502L617,562Q628,544 634,523.5Q640,503 640,480Q640,414 593,367Q546,320 480,320Q414,320 367,367Q320,414 320,480Q320,546 367,593Q414,640 480,640Z"/> +</vector> diff --git a/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml b/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..6309dcbf8615da7717347150d20927be5a319de1 --- /dev/null +++ b/jami-android/app/src/main/res/drawable/baseline_settings_ethernet_24.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M7.77,6.76L6.23,5.48 0.82,12l5.41,6.52 1.54,-1.28L3.42,12l4.35,-5.24zM7,13h2v-2L7,11v2zM17,11h-2v2h2v-2zM11,13h2v-2h-2v2zM17.77,5.48l-1.54,1.28L20.58,12l-4.35,5.24 1.54,1.28L23.18,12l-5.41,-6.52z" /> + +</vector> diff --git a/jami-android/app/src/main/res/drawable/p2p_24.xml b/jami-android/app/src/main/res/drawable/p2p_24.xml new file mode 100644 index 0000000000000000000000000000000000000000..97e5faed4bed50c8c483297089c1156d47d7349f --- /dev/null +++ b/jami-android/app/src/main/res/drawable/p2p_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,160Q80,127 103.5,103.5Q127,80 160,80L360,80Q393,80 416.5,103.5Q440,127 440,160L440,360L360,360L360,280L160,280L160,600L440,600L440,720Q440,753 416.5,776.5Q393,800 360,800L160,800ZM600,880Q567,880 543.5,856.5Q520,833 520,800L520,600L600,600L600,680L800,680L800,360L520,360L520,240Q520,207 543.5,183.5Q567,160 600,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,800Q880,833 856.5,856.5Q833,880 800,880L600,880ZM320,520Q303,520 291.5,508.5Q280,497 280,480Q280,463 291.5,451.5Q303,440 320,440Q337,440 348.5,451.5Q360,463 360,480Q360,497 348.5,508.5Q337,520 320,520ZM480,520Q463,520 451.5,508.5Q440,497 440,480Q440,463 451.5,451.5Q463,440 480,440Q497,440 508.5,451.5Q520,463 520,480Q520,497 508.5,508.5Q497,520 480,520ZM640,520Q623,520 611.5,508.5Q600,497 600,480Q600,463 611.5,451.5Q623,440 640,440Q657,440 668.5,451.5Q680,463 680,480Q680,497 668.5,508.5Q657,520 640,520Z"/> +</vector> diff --git a/jami-android/app/src/main/res/layout/frag_connection_monitor.xml b/jami-android/app/src/main/res/layout/frag_connection_monitor.xml new file mode 100644 index 0000000000000000000000000000000000000000..36c1f6f2fcbf65040c041a82ce6730dba7b9170b --- /dev/null +++ b/jami-android/app/src/main/res/layout/frag_connection_monitor.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingVertical="8dp" + android:clipToPadding="false" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/item_device_connection" /> \ No newline at end of file diff --git a/jami-android/app/src/main/res/layout/frag_settings.xml b/jami-android/app/src/main/res/layout/frag_settings.xml index 293617745f888721abae5791f165635ea1464656..84005e9428b941da61c306c96ff7ab970abc28e3 100644 --- a/jami-android/app/src/main/res/layout/frag_settings.xml +++ b/jami-android/app/src/main/res/layout/frag_settings.xml @@ -757,7 +757,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:id="@+id/system_diagnostics_image" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:contentDescription="@string/pref_persistNotification_summary" + android:contentDescription="@string/pref_logs_title" android:src="@drawable/baseline_article_24" android:layout_alignParentStart="true" android:layout_centerVertical="true" @@ -788,6 +788,49 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. </RelativeLayout> + + <RelativeLayout + android:id="@+id/connection_monitor" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/padding_large"> + + <ImageView + android:id="@+id/connection_monitor_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/pref_connection_monitor_summary" + android:src="@drawable/baseline_settings_ethernet_24" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="32dp"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@+id/connection_monitor_image" + android:orientation="vertical"> + + <TextView + style="@style/ListPrimary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" + android:text="@string/pref_connection_monitor" /> + + <TextView + style="@style/ListSecondary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/pref_logs_summary" /> + + </LinearLayout> + + </RelativeLayout> + + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/jami-android/app/src/main/res/layout/item_device_connection.xml b/jami-android/app/src/main/res/layout/item_device_connection.xml new file mode 100644 index 0000000000000000000000000000000000000000..3cea00bf00ca00f9288eaa453ae3d61bd07f9c19 --- /dev/null +++ b/jami-android/app/src/main/res/layout/item_device_connection.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="56dp" + android:orientation="horizontal" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_centerVertical="true" + android:layout_marginStart="8dp" + android:layout_marginEnd="16dp" + android:src="@drawable/baseline_private_connectivity_24" + tools:tint="@color/green_500" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginEnd="8dp" + android:layout_toEndOf="@+id/icon" + android:orientation="vertical"> + + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="middle" + android:fontFamily="monospace" + android:maxLines="1" + android:singleLine="true" + android:textColor="@color/textColorSecondary" + android:textSize="@dimen/text_size_small" + tools:text="@tools:sample/full_names" /> + + <TextView + android:id="@+id/device" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="middle" + android:fontFamily="monospace" + android:maxLines="1" + android:singleLine="true" + android:textSize="@dimen/text_size_small" + tools:text="@tools:sample/us_zipcodes" /> + </LinearLayout> +</RelativeLayout> \ No newline at end of file diff --git a/jami-android/app/src/main/res/layout/item_list_contact.xml b/jami-android/app/src/main/res/layout/item_list_contact.xml new file mode 100644 index 0000000000000000000000000000000000000000..45ebd8076e5dead98da3a5d62f0fea269e21ccce --- /dev/null +++ b/jami-android/app/src/main/res/layout/item_list_contact.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2024 Savoir-faire Linux Inc. + +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 <https://www.gnu.org/licenses/>. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="@drawable/background_item_smartlist" + android:foreground="?attr/selectableItemBackground" + android:descendantFocusability="blocksDescendants" + android:paddingLeft="16dp" + android:paddingTop="8dp" + android:paddingRight="16dp" + android:paddingBottom="8dp" + android:layout_marginTop="8dp"> + + <cx.ring.views.AvatarView + android:id="@+id/photo" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_centerVertical="true" + android:layout_marginEnd="16dp" + tools:uri="@tools:sample/us_phones" + tools:avatar="@tools:sample/avatars" /> + + <LinearLayout + android:id="@+id/conv_info" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@+id/photo"> + + <TextView + android:id="@+id/conv_participant" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="middle" + android:gravity="start" + android:singleLine="true" + android:maxLines="1" + android:textAlignment="viewStart" + android:textColor="?attr/colorOnSurface" + android:textSize="@dimen/text_size_medium" + tools:text="@tools:sample/full_names" + android:paddingBottom="3dp"/> + + <TextView + android:id="@+id/conv_last_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:gravity="start" + android:maxLines="1" + android:textAlignment="viewStart" + android:textColor="@color/textColorSecondary" + android:textSize="@dimen/text_size_small" + tools:text="Ongoing call of 56 secs" /> + + </LinearLayout> + +</RelativeLayout> diff --git a/jami-android/app/src/main/res/values/strings_preferences.xml b/jami-android/app/src/main/res/values/strings_preferences.xml index 7efd706445fc610719230f2ca8a961b5b2f56a29..38a3ceea4220d5ad7e21b3a5bf44337c367d0dde 100644 --- a/jami-android/app/src/main/res/values/strings_preferences.xml +++ b/jami-android/app/src/main/res/values/strings_preferences.xml @@ -63,6 +63,9 @@ <string name="pref_logs_start">Start Logging</string> <string name="pref_logs_stop">Stop logging</string> + <string name="pref_connection_monitor">Connection monitor</string> + <string name="pref_connection_monitor_summary">Monitor peer to peer connections</string> + <string name="pref_typing_title">Enable typing indicators</string> <string name="pref_typing_summary">Send and receive typing indicators showing that a message is being typed.</string> diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt index 929d33316a56c7a00edec95d51cdf6fe9e5af823..87b85915b376a5941632402377c044926350fa58 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt @@ -1663,6 +1663,51 @@ class AccountService( } } + enum class ConnectionStatus(val value: Int) { + Connected(0), TLS(1), ICE(2), Connecting(3), Waiting(4); + companion object { + fun fromInt(state: Int) = ConnectionStatus.entries[state] + } + } + + data class DeviceConnection( + val accountId: String, + val id: String, + val device: String, + val status: ConnectionStatus, + val peer: String, + val remoteAddress: String? + ) + + fun monitorConnections(): Observable<Pair<String, List<Pair<String, List<DeviceConnection>>>>> = + currentAccountSubject + .switchMap { monitorConnections(it.accountId) } + + private fun monitorConnections(accountId: String): Observable<Pair<String, List<Pair<String, List<DeviceConnection>>>>> = + Observable.interval(0, 1, TimeUnit.SECONDS, scheduler) + .map { _ -> + Pair(accountId, JamiService.getConnectionList(accountId, "") + .mapNotNull { it: Map<String, String?> -> + val status = ConnectionStatus.fromInt(it["status"]?.toInt() ?: 4) + if (status == ConnectionStatus.Waiting || status == ConnectionStatus.Connecting) { + null + } else { + DeviceConnection( + accountId=accountId, + id=it["id"]!!, + device=it["device"]!!, + status=status, + peer=it["peer"]!!, + remoteAddress=it["remoteAddress"] + ) + } + } + .groupBy { it.peer } + .map { Pair(it.key, it.value) } + .sortedBy { it.first } + ) + } + companion object { private val TAG = AccountService::class.java.simpleName private const val VCARD_CHUNK_SIZE = 1000