From fca39521cc1ec988a4f3f3471bb924abdeb21416 Mon Sep 17 00:00:00 2001 From: Chumva Date: Wed, 10 Apr 2019 18:00:45 +0300 Subject: [PATCH] Search initial commit --- .../drawable/gradient_both_sides_light.xml | 21 + OsmAnd-telegram/res/layout/activity_main.xml | 30 +- .../res/layout/bottom_buttons_bar.xml | 30 + .../res/layout/empty_state_search.xml | 67 +++ .../res/layout/fragment_my_location_tab.xml | 85 ++- .../res/layout/fragment_search_dialog.xml | 92 +++ OsmAnd-telegram/res/layout/user_list_item.xml | 4 + OsmAnd-telegram/res/values/strings.xml | 4 + .../osmand/telegram/helpers/TelegramHelper.kt | 86 ++- .../osmand/telegram/ui/LiveNowTabFragment.kt | 2 + .../telegram/ui/MyLocationTabFragment.kt | 5 +- .../telegram/ui/SearchDialogFragment.kt | 527 ++++++++++++++++++ .../telegram/ui/SetTimeDialogFragment.kt | 6 + .../ui/views/EmptyStateRecyclerView.kt | 56 ++ .../telegram/utils/OsmandLocationUtils.kt | 5 +- 15 files changed, 946 insertions(+), 74 deletions(-) create mode 100644 OsmAnd-telegram/res/drawable/gradient_both_sides_light.xml create mode 100644 OsmAnd-telegram/res/layout/bottom_buttons_bar.xml create mode 100644 OsmAnd-telegram/res/layout/empty_state_search.xml create mode 100644 OsmAnd-telegram/res/layout/fragment_search_dialog.xml create mode 100644 OsmAnd-telegram/src/net/osmand/telegram/ui/SearchDialogFragment.kt create mode 100644 OsmAnd-telegram/src/net/osmand/telegram/ui/views/EmptyStateRecyclerView.kt diff --git a/OsmAnd-telegram/res/drawable/gradient_both_sides_light.xml b/OsmAnd-telegram/res/drawable/gradient_both_sides_light.xml new file mode 100644 index 0000000000..89f6d1c4ca --- /dev/null +++ b/OsmAnd-telegram/res/drawable/gradient_both_sides_light.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/activity_main.xml b/OsmAnd-telegram/res/layout/activity_main.xml index 8b5341d7c2..dea3e9ba59 100644 --- a/OsmAnd-telegram/res/layout/activity_main.xml +++ b/OsmAnd-telegram/res/layout/activity_main.xml @@ -28,34 +28,10 @@ - - - - - - - - - + android:layout_height="@dimen/buttons_bottom_bar_height" /> + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/empty_state_search.xml b/OsmAnd-telegram/res/layout/empty_state_search.xml new file mode 100644 index 0000000000..1f0eb39653 --- /dev/null +++ b/OsmAnd-telegram/res/layout/empty_state_search.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/fragment_my_location_tab.xml b/OsmAnd-telegram/res/layout/fragment_my_location_tab.xml index 000d64192a..d8cfc2311c 100644 --- a/OsmAnd-telegram/res/layout/fragment_my_location_tab.xml +++ b/OsmAnd-telegram/res/layout/fragment_my_location_tab.xml @@ -101,49 +101,6 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/user_list_item.xml b/OsmAnd-telegram/res/layout/user_list_item.xml index 7dd6034ec0..dad854c75f 100644 --- a/OsmAnd-telegram/res/layout/user_list_item.xml +++ b/OsmAnd-telegram/res/layout/user_list_item.xml @@ -7,6 +7,10 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + Search contacts + Search across all of your groups and contacts. + Type contact or group name + Search OK Timeline is a feature available now for free. Disable monitoring diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index 17bc313815..d70354f8e5 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -35,6 +35,8 @@ class TelegramHelper private constructor() { private const val IGNORED_ERROR_CODE = 406 private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5 + private const val MAX_SEARCH_ITEMS = 100 + // min and max values for the Telegram API const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61 const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day @@ -98,6 +100,7 @@ class TelegramHelper private constructor() { private val incomingMessagesListeners = HashSet() private val outgoingMessagesListeners = HashSet() private val fullInfoUpdatesListeners = HashSet() + private val searchListeners = HashSet() fun addIncomingMessagesListener(listener: TelegramIncomingMessagesListener) { incomingMessagesListeners.add(listener) @@ -123,6 +126,14 @@ class TelegramHelper private constructor() { fullInfoUpdatesListeners.remove(listener) } + fun addSearchListener(listener: TelegramSearchListener) { + searchListeners.add(listener) + } + + fun removeSearchListener(listener: TelegramSearchListener) { + searchListeners.remove(listener) + } + fun getChatList(): TreeSet { synchronized(chatList) { return TreeSet(chatList.filter { !it.isChannel }) @@ -193,7 +204,7 @@ class TelegramHelper private constructor() { fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret - private fun isChannel(chat: TdApi.Chat): Boolean { + fun isChannel(chat: TdApi.Chat): Boolean { return chat.type is TdApi.ChatTypeSupergroup && (chat.type as TdApi.ChatTypeSupergroup).isChannel } @@ -249,6 +260,12 @@ class TelegramHelper private constructor() { fun onTelegramAuthorizationRequestError(code: Int, message: String) } + interface TelegramSearchListener { + fun onSearchChatsFinished(obj: TdApi.Chats) + fun onSearchPublicChatsFinished(obj: TdApi.Chats) + fun onSearchContactsFinished(obj: TdApi.Users) + } + inner class TelegramAuthorizationRequestHandler(val telegramAuthorizationRequestListener: TelegramAuthorizationRequestListener) { fun applyAuthenticationParameter(parameterType: TelegramAuthenticationParameterType, parameterValue: String) { @@ -628,7 +645,7 @@ class TelegramHelper private constructor() { } } - private fun requestUser(id: Int) { + fun requestUser(id: Int) { client?.send(TdApi.GetUser(id)) { obj -> when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { @@ -648,6 +665,24 @@ class TelegramHelper private constructor() { } } + fun requestChat(id: Long) { + client?.send(TdApi.GetChat(id)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Chat.CONSTRUCTOR -> { + val chat = obj as TdApi.Chat + chats[chat.id] = chat + listener?.onTelegramChatChanged(chat) + } + } + } + } + fun createPrivateChatWithUser( userId: Int, shareInfo: TelegramSettings.ShareChatInfo, @@ -965,6 +1000,53 @@ class TelegramHelper private constructor() { } } + fun searchChats(searchTerm: String) { + client?.send(TdApi.SearchChats(searchTerm, MAX_SEARCH_ITEMS)) { obj -> + checkChatsAndUsersSearch(obj) + } + } + + fun searchChatsOnServer(searchTerm: String) { + client?.send(TdApi.SearchChatsOnServer(searchTerm, MAX_SEARCH_ITEMS)) { obj -> + checkChatsAndUsersSearch(obj) + } + } + + fun searchPublicChats(searchTerm: String) { + client?.send(TdApi.SearchPublicChats(searchTerm)) { obj -> + checkChatsAndUsersSearch(obj, true) + } + } + + fun searchContacts(searchTerm: String) { + client?.send(TdApi.SearchContacts(searchTerm, MAX_SEARCH_ITEMS)) { obj -> + checkChatsAndUsersSearch(obj) + } + } + + private fun checkChatsAndUsersSearch(obj: TdApi.Object, publicChats: Boolean = false) { + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Chats.CONSTRUCTOR -> { + val chats = obj as TdApi.Chats + if (publicChats) { + searchListeners.forEach { it.onSearchPublicChatsFinished(chats) } + } else { + searchListeners.forEach { it.onSearchChatsFinished(chats) } + } + } + TdApi.Users.CONSTRUCTOR -> { + val users = obj as TdApi.Users + searchListeners.forEach { it.onSearchContactsFinished(users) } + } + } + } + fun logout(): Boolean { return if (libraryLoaded) { haveAuthorization = false diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt index b1589b96c8..f26692bb7c 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt @@ -510,6 +510,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage holder.topDivider?.visibility = if (!sortByGroup && position != 0) View.GONE else View.VISIBLE } else if (item is LocationItem && holder is ContactViewHolder) { holder.description?.text = OsmandFormatter.getListItemLiveTimeDescr(app, item.lastUpdated, lastResponseStr) + holder.topShadowDivider?.visibility = View.GONE } } @@ -601,6 +602,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage val description: TextView? = view.findViewById(R.id.description) val receivedGpxPointsContainer: View? = view.findViewById(R.id.received_gps_points_container) val receivedGpxPointsDescr: TextView? = view.findViewById(R.id.received_gps_points_description) + val topShadowDivider: View? = view.findViewById(R.id.top_divider) val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) val lastTelegramUpdateTime: TextView? = view.findViewById(R.id.last_telegram_update_time) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt index 0f881ea783..774809d0c0 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt @@ -130,6 +130,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { appBarCollapsed = collapsed adjustText() adjustAppbar() + adjustSearchBox() optionsBtn.visibility = if (collapsed) View.VISIBLE else View.GONE } }) @@ -183,7 +184,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { setBackgroundDrawable(searchBoxBg) } findViewById(R.id.search_button).setOnClickListener { - Toast.makeText(context, "Search", Toast.LENGTH_SHORT).show() + activity.supportFragmentManager?.also { SearchDialogFragment.showInstance(it, this@MyLocationTabFragment) } } findViewById(R.id.search_icon) .setImageDrawable(app.uiUtils.getThemedIcon(R.drawable.ic_action_search_dark)) @@ -635,6 +636,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { } } } + holder.topShadowDivider?.visibility = View.GONE holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE holder.itemView.setOnClickListener { if (live) { @@ -754,6 +756,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { inner class ChatViewHolder(val view: View) : BaseViewHolder(view) { val checkBox: CheckBox? = view.findViewById(R.id.check_box) + val topShadowDivider: View? = view.findViewById(R.id.top_divider) val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SearchDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SearchDialogFragment.kt new file mode 100644 index 0000000000..08d6e1c36b --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SearchDialogFragment.kt @@ -0,0 +1,527 @@ +package net.osmand.telegram.ui + +import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.Toolbar +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import net.osmand.Location +import net.osmand.PlatformUtil +import net.osmand.data.LatLon +import net.osmand.telegram.R +import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener +import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener +import net.osmand.telegram.helpers.TelegramHelper +import net.osmand.telegram.helpers.TelegramUiHelper +import net.osmand.telegram.ui.views.EmptyStateRecyclerView +import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandFormatter +import net.osmand.telegram.utils.OsmandLocationUtils +import net.osmand.telegram.utils.UiUtils +import net.osmand.util.MapUtils +import org.drinkless.td.libcore.telegram.TdApi + +class SearchDialogFragment : BaseDialogFragment(), TelegramHelper.TelegramSearchListener, + TelegramLocationListener, TelegramCompassListener { + + private val log = PlatformUtil.getLog(SearchDialogFragment::class.java) + + private val uiUtils get() = app.uiUtils + + private val adapter = SearchAdapter() + + private lateinit var locationViewCache: UiUtils.UpdateLocationViewCache + + private lateinit var searchBox: EditText + private lateinit var buttonsBar: LinearLayout + + private val searchedChatsIds = mutableSetOf() + private val searchedPublicChatsIds = mutableSetOf() + private val searchedContactsIds = mutableSetOf() + + private val selectedChats = HashSet() + private val selectedUsers = HashSet() + + private var searchQuery: String = "" + + private var location: Location? = null + private var heading: Float? = null + private var locationUiUpdateAllowed: Boolean = true + + override fun onCreateView( + inflater: LayoutInflater, + parent: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val mainView = inflater.inflate(R.layout.fragment_search_dialog, parent) + + val appBarLayout = mainView.findViewById(R.id.app_bar_layout) + AndroidUtils.addStatusBarPadding19v(context!!, appBarLayout) + + mainView.findViewById(R.id.toolbar).apply { + navigationIcon = uiUtils.getThemedIcon(R.drawable.ic_arrow_back) + setNavigationOnClickListener { dismiss() } + } + + searchBox = mainView.findViewById(R.id.searchEditText).apply { + addTextChangedListener(object : TextWatcher { + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun afterTextChanged(s: Editable) { + val newQueryText = s.toString() + if (!searchQuery.equals(newQueryText, true)) { + searchQuery = newQueryText + clearSearchedItems() + if (searchQuery.isNotBlank()) { + runSearch() + } else { + updateList() + } + } + } + }) + } + mainView.findViewById(R.id.search_icon).setOnClickListener { + runSearch() + } + val emptyView = mainView.findViewById(R.id.empty_view) + mainView.findViewById(R.id.recycler_view).apply { + layoutManager = LinearLayoutManager(context) + adapter = this@SearchDialogFragment.adapter + setEmptyView(emptyView) + addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + locationUiUpdateAllowed = newState == RecyclerView.SCROLL_STATE_IDLE + } + }) + } + + val matrix = ColorMatrix() + matrix.setSaturation(0f) + val filter = ColorMatrixColorFilter(matrix) + + mainView.findViewById(R.id.empty_state_background).colorFilter = filter + mainView.findViewById(R.id.empty_state_placeholder).colorFilter = filter + + buttonsBar = mainView.findViewById(R.id.buttons_bar).apply { + findViewById(R.id.primary_btn).apply { + text = getString(R.string.shared_string_continue) + setOnClickListener { + onPrimaryBtnClick() + } + } + findViewById(R.id.secondary_btn).apply { + text = getString(R.string.shared_string_cancel) + setOnClickListener { + onSecondaryBtnClick() + } + } + } + + return mainView + } + + private fun clearSearchedItems() { + searchedChatsIds.clear() + searchedPublicChatsIds.clear() + searchedContactsIds.clear() + } + + private fun runSearch() { + if (searchQuery.isNotBlank()) { + runSearch(searchQuery) + } + } + + private fun runSearch(text: String) { + telegramHelper.searchChats(text) + telegramHelper.searchChatsOnServer(text) + telegramHelper.searchContacts(text) + if (text.length > 4) { + telegramHelper.searchPublicChats(text) + } + } + + override fun onResume() { + super.onResume() + telegramHelper.addSearchListener(this) + locationViewCache = app.uiUtils.getUpdateLocationViewCache() + startLocationUpdate() + } + + override fun onPause() { + super.onPause() + telegramHelper.removeSearchListener(this) + stopLocationUpdate() + } + + override fun updateLocation(location: Location?) { + val loc = this.location + val newLocation = loc == null && location != null + val locationChanged = loc != null && location != null + && loc.latitude != location.latitude + && loc.longitude != location.longitude + if (newLocation || locationChanged) { + this.location = location + updateLocationUi() + } + } + + override fun updateCompassValue(value: Float) { + // 99 in next line used to one-time initialize arrows (with reference vs. fixed-north direction) + // on non-compass devices + val lastHeading = heading ?: 99f + heading = value + if (Math.abs(MapUtils.degreesDiff(lastHeading.toDouble(), value.toDouble())) > 5) { + updateLocationUi() + } else { + heading = lastHeading + } + } + + private fun startLocationUpdate() { + app.locationProvider.addLocationListener(this) + app.locationProvider.addCompassListener(this) + updateLocationUi() + } + + private fun stopLocationUpdate() { + app.locationProvider.removeLocationListener(this) + app.locationProvider.removeCompassListener(this) + } + + private fun updateLocationUi() { + if (locationUiUpdateAllowed) { + app.runInUIThread { updateList() } + } + } + + private fun updateList() { + val items: MutableList = mutableListOf() + val currentUserId = telegramHelper.getCurrentUserId() + + val chats: MutableList = getChats(currentUserId) + items.addAll(chats) + + val users: MutableList = getContacts(currentUserId, chats) + items.addAll(sortUsers(users)) + + items.addAll(getChats(currentUserId, chats)) + + adapter.items = items + } + + private fun getChats( + currentUserId: Int, + addedChats: List? = null + ): MutableList { + val chats: MutableList = mutableListOf() + searchedChatsIds.forEach { + val chat = telegramHelper.getChat(it) + if (chat != null) { + if (!telegramHelper.isChannel(chat) + && telegramHelper.getUserIdFromChatType(chat.type) != currentUserId + && (addedChats == null || (!addedChats.contains(chat))) + ) { + chats.add(chat) + } + } else { + telegramHelper.requestChat(it) + } + } + return chats + } + + private fun getContacts(currentUserId: Int, chats: List): MutableList { + val users: MutableList = mutableListOf() + searchedContactsIds.forEach { userId -> + val user = telegramHelper.getUser(userId) + if (user != null) { + if (user.id != currentUserId && !chats.any { telegramHelper.getUserIdFromChatType(it.type) == user.id }) + users.add(user) + } else { + telegramHelper.requestUser(userId) + } + } + return users + } + + private fun sortUsers(list: MutableList): MutableList { + list.sortWith(Comparator { o1, o2 -> + val title1 = TelegramUiHelper.getUserName(o1) + val title2 = TelegramUiHelper.getUserName(o2) + title1.compareTo(title2) + }) + return list + } + + override fun onSearchContactsFinished(obj: TdApi.Users) { + log.debug("searchContactsFinished $obj") + val ids = obj.userIds + if (ids.isNotEmpty()) { + searchedContactsIds.addAll(ids.toList()) + app.runInUIThread { updateList() } + } + } + + override fun onSearchChatsFinished(obj: TdApi.Chats) { + log.debug("searchChatsFinished $obj") + val ids = obj.chatIds + if (ids.isNotEmpty()) { + searchedChatsIds.addAll(ids.toList()) + app.runInUIThread { updateList() } + } + } + + override fun onSearchPublicChatsFinished(obj: TdApi.Chats) { + log.debug("onSearchPublicChatsFinished $obj") + val ids = obj.chatIds + if (ids.isNotEmpty()) { + searchedPublicChatsIds.addAll(ids.toList()) + app.runInUIThread { updateList() } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + LogoutBottomSheet.LOGOUT_REQUEST_CODE -> { + dismiss() + } + SetTimeDialogFragment.LOCATION_SHARED_REQUEST_CODE -> { + if (resultCode == SetTimeDialogFragment.LOCATION_SHARED_REQUEST_CODE) { + targetFragment?.also { + it.onActivityResult(targetRequestCode, resultCode, null) + } + dismiss() + } + } + } + } + + inner class SearchAdapter : RecyclerView.Adapter() { + + var items = mutableListOf() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.user_list_item, parent, false) + return ChatViewHolder(view) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { + val item = items[position] + val isChat = item is TdApi.Chat + val itemId = if (isChat) { + (item as TdApi.Chat).id + } else { + (item as TdApi.User).id.toLong() + } + val latLon = getItemLastLocation(item) + val lastUpdate = getLastUpdateTime(item) + + val lastItem = position == itemCount - 1 + val placeholderId = if (isChat && telegramHelper.isGroup(item as TdApi.Chat)) R.drawable.img_group_picture else R.drawable.img_user_picture + val live = (isChat && settings.isSharingLocationToChat(itemId)) + val shareInfo = if (isChat) settings.getChatsShareInfo()[itemId] else null + + val photoPath = when (item) { + is TdApi.Chat -> item.photo?.small?.local?.path + is TdApi.User -> item.profilePhoto?.small?.local?.path + else -> null + } + + TelegramUiHelper.setupPhoto(app, holder.icon, photoPath, placeholderId, false) + + val title = when (item) { + is TdApi.Chat -> item.title + is TdApi.User -> TelegramUiHelper.getUserName(item) + else -> null + } + + holder.title?.text = title + + holder.checkBox?.apply { + visibility = if (live) View.GONE else View.VISIBLE + setOnCheckedChangeListener(null) + isChecked = if (isChat) { + selectedChats.contains(itemId) + } else { + selectedUsers.contains(itemId) + } + setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + if (isChat) { + selectedChats.add(itemId) + } else { + selectedUsers.add(itemId) + } + } else { + if (isChat) { + selectedChats.remove(itemId) + } else { + selectedUsers.remove(itemId) + } + } + switchButtonsVisibility(selectedChats.isNotEmpty() || selectedUsers.isNotEmpty()) + } + } + holder.topShadowDivider?.visibility = if (position == 0) View.VISIBLE else View.GONE + holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE + holder.itemView.setOnClickListener { + if (!live) { + holder.checkBox?.apply { + isChecked = !isChecked + } + } + } + + if (location != null && latLon != null && lastUpdate != null) { + val staleLocation = System.currentTimeMillis() / 1000 - lastUpdate > settings.staleLocTime + + holder.locationViewContainer?.visibility = if (lastUpdate > 0) View.VISIBLE else View.GONE + locationViewCache.outdatedLocation = staleLocation + app.uiUtils.updateLocationView(holder.directionIcon, holder.distanceText, latLon, locationViewCache) + } else { + holder.locationViewContainer?.visibility = View.GONE + } + + val expiresIn = shareInfo?.getChatLiveMessageExpireTime() ?: 0 + holder.textInArea?.apply { + visibility = if (live) View.VISIBLE else View.GONE + text = OsmandFormatter.getFormattedDuration(app, expiresIn) + } + + holder.description?.apply { + val description = getItemDescription(item, lastUpdate) + text = description + visibility = if (description != null) View.VISIBLE else View.GONE + } + } + + private fun getItemLastMessage(item: TdApi.Object): TdApi.Message? { + when (item) { + is TdApi.User -> { + return telegramHelper.getUserMessage(item) + } + is TdApi.Chat -> { + return telegramHelper.getChatMessages(item.id).firstOrNull() ?: item.lastMessage + } + } + return null + } + + private fun getItemLastLocation(item: TdApi.Object): LatLon? { + val message = getItemLastMessage(item) + if (message != null && OsmandLocationUtils.getSenderMessageId(message) != telegramHelper.getCurrentUserId()) { + val messageLocation = OsmandLocationUtils.parseMessageContent(message, telegramHelper) + if (messageLocation != null) { + return LatLon(messageLocation.lat, messageLocation.lon) + } + } + return null + } + + private fun getLastUpdateTime(item: TdApi.Object): Int? { + val message = getItemLastMessage(item) + if (message != null && OsmandLocationUtils.getSenderMessageId(message) != telegramHelper.getCurrentUserId()) { + return OsmandLocationUtils.getLastUpdatedTime(message) + } + + return null + } + + private fun getItemDescription(item: TdApi.Object, lastUpdateTime: Int?): String? { + if (lastUpdateTime != null) { + return OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdateTime) + } + if (item is TdApi.Chat && telegramHelper.isGroup(item)) { + return getString(R.string.shared_string_group) + } + + return null + } + + override fun getItemCount() = items.size + + inner class ChatViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val icon: ImageView? = view.findViewById(R.id.icon) + val title: TextView? = view.findViewById(R.id.title) + val locationViewContainer: View? = view.findViewById(R.id.location_view_container) + val directionIcon: ImageView? = view.findViewById(R.id.direction_icon) + val distanceText: TextView? = view.findViewById(R.id.distance_text) + val description: TextView? = view.findViewById(R.id.description) + val checkBox: CheckBox? = view.findViewById(R.id.check_box) + val textInArea: TextView? = view.findViewById(R.id.text_in_area) + val topShadowDivider: View? = view.findViewById(R.id.top_divider) + val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) + } + } + + private fun onPrimaryBtnClick() { + if (selectedChats.isNotEmpty() || selectedUsers.isNotEmpty()) { + fragmentManager?.also { + SetTimeDialogFragment.showInstance(it, selectedChats, selectedUsers, this) + } + } + } + + private fun onSecondaryBtnClick() { + clearSelection() + adapter.notifyDataSetChanged() + adapter.notifyDataSetChanged() + switchButtonsVisibility(false) + } + + private fun clearSelection() { + selectedChats.clear() + selectedUsers.clear() + } + + private fun switchButtonsVisibility(visible: Boolean) { + val buttonsVisibility = if (visible) View.VISIBLE else View.GONE + if (buttonsBar.visibility != buttonsVisibility) { + buttonsBar.visibility = buttonsVisibility + } + } + + companion object { + + const val TAG = "SearchDialogFragment" + + private val SEARCH_ITEM = 1 + + fun showInstance(fm: FragmentManager, target: Fragment?): Boolean { + return try { + SearchDialogFragment().apply { + if (target != null) { + setTargetFragment(target, SetTimeDialogFragment.LOCATION_SHARED_REQUEST_CODE) + } + show(fm, TAG) + } + true + } catch (e: RuntimeException) { + false + } + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt index c3007c9217..d625da03a7 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt @@ -79,6 +79,9 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te view.findViewById(R.id.secondary_btn).apply { text = getString(R.string.shared_string_back) setOnClickListener { + targetFragment?.also { + it.onActivityResult(targetRequestCode, LOCATION_SHARING_CANCELED_CODE, null) + } dismiss() } } @@ -357,6 +360,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te userLivePeriods[itemId]?.also { text = formatLivePeriod(it) } } } + holder.topShadowDivider?.visibility = View.GONE holder.bottomShadow?.visibility = View.GONE holder.itemView.setOnClickListener { selectDuration(itemId, isChat) @@ -373,6 +377,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te val locationViewContainer: View? = view.findViewById(R.id.location_view_container) val description: TextView? = view.findViewById(R.id.description) val textInArea: TextView? = view.findViewById(R.id.text_in_area) + val topShadowDivider: View? = view.findViewById(R.id.top_divider) val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) } } @@ -380,6 +385,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te companion object { const val LOCATION_SHARED_REQUEST_CODE = 0 + const val LOCATION_SHARING_CANCELED_CODE = 1 private const val TAG = "SetTimeDialogFragment" private const val CHATS_KEY = "chats_key" diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/views/EmptyStateRecyclerView.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/views/EmptyStateRecyclerView.kt new file mode 100644 index 0000000000..4903284533 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/views/EmptyStateRecyclerView.kt @@ -0,0 +1,56 @@ +package net.osmand.telegram.ui.views + +import android.content.Context +import android.support.v7.widget.RecyclerView +import android.util.AttributeSet +import android.view.View + +class EmptyStateRecyclerView : RecyclerView { + + private var emptyView: View? = null + + private val emptyStateObserver = object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + checkIfEmpty() + } + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + checkIfEmpty() + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + checkIfEmpty() + } + } + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( + context, + attrs, + defStyle + ) + + override fun setAdapter(adapter: RecyclerView.Adapter<*>?) { + val oldAdapter = getAdapter() + oldAdapter?.unregisterAdapterDataObserver(emptyStateObserver) + super.setAdapter(adapter) + adapter?.registerAdapterDataObserver(emptyStateObserver) + checkIfEmpty() + } + + fun setEmptyView(emptyView: View) { + this.emptyView = emptyView + checkIfEmpty() + } + + private fun checkIfEmpty() { + adapter?.apply { + val empty = itemCount == 0 + visibility = if (empty) View.GONE else View.VISIBLE + emptyView?.visibility = if (empty) View.VISIBLE else View.GONE + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt index eaeb328fce..f4a50e7a9d 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt @@ -169,7 +169,10 @@ object OsmandLocationUtils { } } - fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean): MessageLocation { + fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean): MessageLocation? { + if (botLocation && !text.text.startsWith(DEVICE_PREFIX) || !botLocation && !text.text.startsWith(USER_TEXT_LOCATION_TITLE)) { + return null + } val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation() res.type = LocationMessages.TYPE_TEXT var locationNA = false