diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 5eaec2c84b..c33185ba26 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3267,6 +3267,12 @@ Last activity %1$s Session details Application, device, and activity information. + Session + Session name + Session ID + Last activity + Device + IP address %s\nis looking a little empty. diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt new file mode 100644 index 0000000000..0bfcc371c5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionDeviceIsVisibleUseCase.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.details + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import javax.inject.Inject + +// TODO add unit tests +class CheckIfSectionDeviceIsVisibleUseCase @Inject constructor() { + + fun execute(deviceInfo: DeviceInfo): Boolean { + return deviceInfo.lastSeenIp?.isNotEmpty().orFalse() + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt new file mode 100644 index 0000000000..b07d3b7ebf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/CheckIfSectionSessionIsVisibleUseCase.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.details + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import javax.inject.Inject + +// TODO add unit tests +class CheckIfSectionSessionIsVisibleUseCase @Inject constructor() { + + fun execute(deviceInfo: DeviceInfo): Boolean { + return deviceInfo.displayName?.isNotEmpty().orFalse() || + deviceInfo.deviceId?.isNotEmpty().orFalse() || + (deviceInfo.lastSeenTs ?: 0) > 0 + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt index 5c3a46475d..e0f433af35 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsContentItem.kt @@ -25,7 +25,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel @EpoxyModelClass -abstract class SessionDetailsContentItem : VectorEpoxyModel(R.layout.item_session_details_header) { +abstract class SessionDetailsContentItem : VectorEpoxyModel(R.layout.item_session_details_content) { @EpoxyAttribute var title: String? = null diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt new file mode 100644 index 0000000000..5a55ef55ef --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.details + +import androidx.annotation.StringRes +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import javax.inject.Inject + +class SessionDetailsController @Inject constructor( + private val checkIfSectionSessionIsVisibleUseCase: CheckIfSectionSessionIsVisibleUseCase, + private val checkIfSectionDeviceIsVisibleUseCase: CheckIfSectionDeviceIsVisibleUseCase, + private val stringProvider: StringProvider, + private val dateFormatter: VectorDateFormatter, +) : TypedEpoxyController() { + + var callback: Callback? = null + + interface Callback { + fun onItemLongClicked(content: String) + } + + override fun buildModels(data: DeviceInfo?) { + data?.let { info -> + if (hasSectionSession(data)) { + buildSectionSession(info) + } + + if (hasSectionDevice(data)) { + // TODO add a marginTop of 48dp if the session section is visible + buildSectionDevice(info) + } + } + } + + private fun buildHeaderItem(@StringRes titleResId: Int) { + val host = this + sessionDetailsHeaderItem { + id(titleResId) + title(host.stringProvider.getString(titleResId)) + } + } + + private fun buildContentItem(@StringRes titleResId: Int, value: String) { + val host = this + sessionDetailsContentItem { + id(titleResId) + title(host.stringProvider.getString(titleResId)) + description(value) + } + } + + private fun hasSectionSession(data: DeviceInfo): Boolean { + return checkIfSectionSessionIsVisibleUseCase.execute(data) + } + + private fun buildSectionSession(data: DeviceInfo) { + val sessionName = data.displayName + val sessionId = data.deviceId + val sessionLastSeenTs = data.lastSeenTs + + buildHeaderItem(R.string.device_manager_session_details_section_session_title) + + // TODO hide divider on the last visible item + sessionName?.let { + buildContentItem(R.string.device_manager_session_details_session_name, it) + } + sessionId?.let { + buildContentItem(R.string.device_manager_session_details_session_id, it) + } + sessionLastSeenTs?.let { + val formattedDate = dateFormatter.format(it, DateFormatKind.MESSAGE_DETAIL) + buildContentItem(R.string.device_manager_session_details_session_last_activity, formattedDate) + } + } + + private fun hasSectionDevice(data: DeviceInfo): Boolean { + return checkIfSectionDeviceIsVisibleUseCase.execute(data) + } + + private fun buildSectionDevice(data: DeviceInfo) { + val lastSeenIp = data.lastSeenIp + + buildHeaderItem(R.string.device_manager_session_details_section_device_title) + + // TODO hide divider on the last visible item + lastSeenIp?.let { + buildContentItem(R.string.device_manager_session_details_device_ip_address, it) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt index 278f9eec7a..bf0037b0e8 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsFragment.kt @@ -21,11 +21,19 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isGone +import androidx.core.view.isVisible +import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSessionDetailsBinding +import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo +import javax.inject.Inject /** * Display the details info about a Session. @@ -34,6 +42,8 @@ import im.vector.app.databinding.FragmentSessionDetailsBinding class SessionDetailsFragment : VectorBaseFragment() { + @Inject lateinit var sessionDetailsController: SessionDetailsController + private val viewModel: SessionDetailsViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionDetailsBinding { @@ -43,6 +53,7 @@ class SessionDetailsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initToolbar() + initSessionDetails() } private fun initToolbar() { @@ -51,14 +62,33 @@ class SessionDetailsFragment : ?.setTitle(R.string.device_manager_session_details_title) } - /*override fun invalidate() = withState(viewModel) { state -> + private fun initSessionDetails() { + views.sessionDetails.configureWith(sessionDetailsController) + } + + override fun onDestroyView() { + cleanUpSessionDetails() + super.onDestroyView() + } + + private fun cleanUpSessionDetails() { + views.sessionDetails.cleanup() + } + + override fun invalidate() = withState(viewModel) { state -> if (state.deviceInfo is Success) { renderSessionDetails(state.deviceInfo.invoke()) } else { - hideSessionInfo() + hideSessionDetails() } } private fun renderSessionDetails(deviceInfo: DeviceInfo) { - }*/ + views.sessionDetails.isVisible = true + sessionDetailsController.setData(deviceInfo) + } + + private fun hideSessionDetails() { + views.sessionDetails.isGone = true + } } diff --git a/vector/src/main/res/layout/fragment_session_details.xml b/vector/src/main/res/layout/fragment_session_details.xml index 1354408486..de0ce27798 100644 --- a/vector/src/main/res/layout/fragment_session_details.xml +++ b/vector/src/main/res/layout/fragment_session_details.xml @@ -1,6 +1,21 @@ + +