mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-18 18:25:03 +01:00
Merge pull request #1284 from nextcloud/feature/1136/share-location
Feature/1136/share location
This commit is contained in:
commit
9c18e1c085
@ -113,7 +113,9 @@ android {
|
||||
packagingOptions {
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/rxjava.properties'
|
||||
}
|
||||
|
||||
@ -267,7 +269,10 @@ dependencies {
|
||||
implementation 'com.github.Kennyc1012:BottomSheet:2.4.1'
|
||||
implementation 'com.github.nextcloud:PopupBubble:master-SNAPSHOT'
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation 'eu.medsea.mimeutil:mime-util:2.1.3'
|
||||
|
||||
implementation('eu.medsea.mimeutil:mime-util:2.1.3', {
|
||||
exclude group: 'org.slf4j'
|
||||
})
|
||||
|
||||
implementation "com.afollestad.material-dialogs:core:${materialDialogsVersion}"
|
||||
implementation "com.afollestad.material-dialogs:datetime:${materialDialogsVersion}"
|
||||
@ -286,6 +291,12 @@ dependencies {
|
||||
implementation 'com.github.tobiaskaminsky:ImagePicker:extraFile-SNAPSHOT'
|
||||
implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
|
||||
|
||||
implementation 'org.osmdroid:osmdroid-android:6.1.10'
|
||||
implementation ('fr.dudie:nominatim-api:3.4', {
|
||||
//noinspection DuplicatePlatformClasses
|
||||
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
|
||||
})
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:3.11.0'
|
||||
testImplementation "org.powermock:powermock-core:${powermockVersion}"
|
||||
|
@ -70,6 +70,9 @@
|
||||
<!-- This permission is deprecated in Android P -->
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<application
|
||||
android:name=".application.NextcloudTalkApplication"
|
||||
android:allowBackup="true"
|
||||
|
42
app/src/main/assets/leafletMapMessagePreview.html
Normal file
42
app/src/main/assets/leafletMapMessagePreview.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='content-Type' content='text/html; charset=UTF-8' />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
<style>
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" ></div>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<script>
|
||||
var queryString = window.location.search;
|
||||
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
var locationLat = urlParams.get('locationLat')
|
||||
var locationLon = urlParams.get('locationLon')
|
||||
var locationGeoLink = urlParams.get('locationGeoLink')
|
||||
var mapProviderUrl = urlParams.get('mapProviderUrl')
|
||||
var mapProviderAttribution = urlParams.get('mapProviderAttribution')
|
||||
|
||||
var map = L.map('map', {
|
||||
zoomControl: false,
|
||||
scrollWheelZoom: false
|
||||
}).setView([locationLat, locationLon], 13);
|
||||
map.dragging.disable();
|
||||
|
||||
L.tileLayer(mapProviderUrl, {
|
||||
attribution: '© ' + mapProviderAttribution
|
||||
}).addTo(map);
|
||||
|
||||
L.marker([locationLat, locationLon]).addTo(map);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.TextView
|
||||
import com.nextcloud.talk.R
|
||||
import fr.dudie.nominatim.model.Address
|
||||
|
||||
class GeocodingAdapter(context: Context, val dataSource: List<Address>) : BaseAdapter() {
|
||||
|
||||
private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
|
||||
override fun getCount(): Int {
|
||||
return dataSource.size
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Any {
|
||||
return dataSource[position]
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val rowView = inflater.inflate(R.layout.geocoding_item, parent, false)
|
||||
|
||||
val nameView = rowView.findViewById(R.id.name) as TextView
|
||||
|
||||
val address = getItem(position) as Address
|
||||
nameView.text = address.displayName
|
||||
|
||||
return rowView
|
||||
}
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.ViewCompat
|
||||
import autodagger.AutoInjector
|
||||
import coil.load
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
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.net.URLEncoder
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
||||
.IncomingTextMessageViewHolder<ChatMessage>(incomingView) {
|
||||
private val binding: ItemCustomIncomingLocationMessageBinding =
|
||||
ItemCustomIncomingLocationMessageBinding.bind(itemView)
|
||||
|
||||
var locationLon: String? = ""
|
||||
var locationLat: String? = ""
|
||||
var locationName: String? = ""
|
||||
var locationGeoLink: String? = ""
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var context: Context? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
setAvatarAndAuthorOnMessageItem(message)
|
||||
|
||||
colorizeMessageBubble(message)
|
||||
|
||||
itemView.isSelected = false
|
||||
binding.messageTime.setTextColor(context?.resources!!.getColor(R.color.warm_grey_four))
|
||||
|
||||
val textSize = context?.resources!!.getDimension(R.dimen.chat_text_size)
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageText.text = message.text
|
||||
binding.messageText.isEnabled = false
|
||||
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
// geo-location
|
||||
setLocationDataOnMessageItem(message)
|
||||
}
|
||||
|
||||
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
||||
val author: String = message.actorDisplayName
|
||||
if (!TextUtils.isEmpty(author)) {
|
||||
binding.messageAuthor.text = author
|
||||
} else {
|
||||
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||
}
|
||||
|
||||
if (!message.isGrouped && !message.isOneToOneConversation) {
|
||||
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||
if (message.actorType == "guests") {
|
||||
// do nothing, avatar is set
|
||||
} else if (message.actorType == "bots" && message.actorId == "changelog") {
|
||||
val layers = arrayOfNulls<Drawable>(2)
|
||||
layers[0] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_background)
|
||||
layers[1] = AppCompatResources.getDrawable(context!!, R.drawable.ic_launcher_foreground)
|
||||
val layerDrawable = LayerDrawable(layers)
|
||||
binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
} else if (message.actorType == "bots") {
|
||||
val drawable = TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
.endConfig()
|
||||
.buildRound(
|
||||
">",
|
||||
context!!.resources.getColor(R.color.black)
|
||||
)
|
||||
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||
binding.messageUserAvatar.setImageDrawable(drawable)
|
||||
}
|
||||
} else {
|
||||
if (message.isOneToOneConversation) {
|
||||
binding.messageUserAvatar.visibility = View.GONE
|
||||
} else {
|
||||
binding.messageUserAvatar.visibility = View.INVISIBLE
|
||||
}
|
||||
binding.messageAuthor.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun colorizeMessageBubble(message: ChatMessage) {
|
||||
val resources = itemView.resources
|
||||
|
||||
var bubbleResource = R.drawable.shape_incoming_message
|
||||
|
||||
if (message.isGrouped) {
|
||||
bubbleResource = R.drawable.shape_grouped_incoming_message
|
||||
}
|
||||
|
||||
val bgBubbleColor = if (message.isDeleted) {
|
||||
resources.getColor(R.color.bg_message_list_incoming_bubble_deleted)
|
||||
} else {
|
||||
resources.getColor(R.color.bg_message_list_incoming_bubble)
|
||||
}
|
||||
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor, bubbleResource
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
}
|
||||
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
val parentChatMessage = message.parentMessage
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedMessageImage.load(it) {
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
binding.messageQuote.quotedMessage.text = parentChatMessage.text
|
||||
|
||||
binding.messageQuote.quotedMessageAuthor
|
||||
.setTextColor(context!!.resources.getColor(R.color.textColorMaxContrast))
|
||||
|
||||
if (parentChatMessage.actorId?.equals(message.activeUser.userId) == true) {
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.colorPrimary)
|
||||
} else {
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
|
||||
}
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
|
||||
private fun setLocationDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.messageParameters != null && message.messageParameters.size > 0) {
|
||||
for (key in message.messageParameters.keys) {
|
||||
val individualHashMap: Map<String, String> = message.messageParameters[key]!!
|
||||
if (individualHashMap["type"] == "geo-location") {
|
||||
locationLon = individualHashMap["longitude"]
|
||||
locationLat = individualHashMap["latitude"]
|
||||
locationName = individualHashMap["name"]
|
||||
locationGeoLink = individualHashMap["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.webview.settings?.javaScriptEnabled = true
|
||||
|
||||
binding.webview.webViewClient = object : WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
|
||||
return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))
|
||||
) {
|
||||
view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html")
|
||||
urlStringBuffer.append(
|
||||
"?mapProviderUrl=" + URLEncoder.encode(context!!.getString(R.string.osm_tile_server_url))
|
||||
)
|
||||
urlStringBuffer.append(
|
||||
"&mapProviderAttribution=" + URLEncoder.encode(context!!.getString(R.string.osm_tile_server_attributation))
|
||||
)
|
||||
urlStringBuffer.append("&locationLat=" + URLEncoder.encode(locationLat))
|
||||
urlStringBuffer.append("&locationLon=" + URLEncoder.encode(locationLon))
|
||||
urlStringBuffer.append("&locationName=" + URLEncoder.encode(locationName))
|
||||
urlStringBuffer.append("&locationGeoLink=" + URLEncoder.encode(locationGeoLink))
|
||||
|
||||
binding.webview.loadUrl(urlStringBuffer.toString())
|
||||
|
||||
binding.webview.setOnTouchListener(object : View.OnTouchListener {
|
||||
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||
when (event?.action) {
|
||||
MotionEvent.ACTION_UP -> openGeoLink()
|
||||
}
|
||||
|
||||
return v?.onTouchEvent(event) ?: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun openGeoLink() {
|
||||
if (!locationGeoLink.isNullOrEmpty()) {
|
||||
val geoLinkWithMarker = addMarkerToGeoLink(locationGeoLink!!)
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(geoLinkWithMarker))
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context!!.startActivity(browserIntent)
|
||||
} else {
|
||||
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "locationGeoLink was null or empty")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMarkerToGeoLink(locationGeoLink: String): String {
|
||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocInMessageView"
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
||||
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
|
||||
public class IncomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder {
|
||||
private final ItemCustomIncomingPreviewMessageBinding binding;
|
||||
|
||||
public IncomingPreviewMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
binding = ItemCustomIncomingPreviewMessageBinding.bind(itemView);
|
||||
}
|
||||
|
||||
public EmojiTextView getMessageText() {
|
||||
return binding.messageText;
|
||||
}
|
||||
|
||||
public ProgressBar getProgressBar() {
|
||||
return binding.progressBar;
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -30,20 +32,14 @@ import android.text.SpannableString
|
||||
import android.text.TextUtils
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import autodagger.AutoInjector
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import coil.load
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
@ -57,41 +53,7 @@ import javax.inject.Inject
|
||||
class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
.IncomingTextMessageViewHolder<ChatMessage>(itemView) {
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageAuthor)
|
||||
var messageAuthor: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageText)
|
||||
var messageText: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageUserAvatar)
|
||||
var messageUserAvatarView: SimpleDraweeView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTimeView: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedChatMessageView)
|
||||
var quotedChatMessageView: RelativeLayout? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageAuthor)
|
||||
var quotedUserName: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageImage)
|
||||
var quotedMessagePreview: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessage)
|
||||
var quotedMessage: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quoteColoredView)
|
||||
var quoteColoredView: View? = null
|
||||
private val binding: ItemCustomIncomingTextMessageBinding = ItemCustomIncomingTextMessageBinding.bind(itemView)
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
@ -101,22 +63,18 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
}
|
||||
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
val author: String = message.actorDisplayName
|
||||
if (!TextUtils.isEmpty(author)) {
|
||||
messageAuthor!!.text = author
|
||||
binding.messageAuthor.text = author
|
||||
} else {
|
||||
messageAuthor!!.setText(R.string.nc_nick_guest)
|
||||
binding.messageAuthor.setText(R.string.nc_nick_guest)
|
||||
}
|
||||
|
||||
if (!message.isGrouped && !message.isOneToOneConversation) {
|
||||
messageUserAvatarView!!.visibility = View.VISIBLE
|
||||
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||
if (message.actorType == "guests") {
|
||||
// do nothing, avatar is set
|
||||
} else if (message.actorType == "bots" && message.actorId == "changelog") {
|
||||
@ -124,7 +82,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
layers[0] = context?.getDrawable(R.drawable.ic_launcher_background)
|
||||
layers[1] = context?.getDrawable(R.drawable.ic_launcher_foreground)
|
||||
val layerDrawable = LayerDrawable(layers)
|
||||
messageUserAvatarView?.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
binding.messageUserAvatar.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
|
||||
} else if (message.actorType == "bots") {
|
||||
val drawable = TextDrawable.builder()
|
||||
.beginConfig()
|
||||
@ -134,16 +92,16 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
">",
|
||||
context!!.resources.getColor(R.color.black)
|
||||
)
|
||||
messageUserAvatarView!!.visibility = View.VISIBLE
|
||||
messageUserAvatarView?.setImageDrawable(drawable)
|
||||
binding.messageUserAvatar.visibility = View.VISIBLE
|
||||
binding.messageUserAvatar.setImageDrawable(drawable)
|
||||
}
|
||||
} else {
|
||||
if (message.isOneToOneConversation) {
|
||||
messageUserAvatarView!!.visibility = View.GONE
|
||||
binding.messageUserAvatar.visibility = View.GONE
|
||||
} else {
|
||||
messageUserAvatarView!!.visibility = View.INVISIBLE
|
||||
binding.messageUserAvatar.visibility = View.INVISIBLE
|
||||
}
|
||||
messageAuthor!!.visibility = View.GONE
|
||||
binding.messageAuthor.visibility = View.GONE
|
||||
}
|
||||
|
||||
val resources = itemView.resources
|
||||
@ -170,7 +128,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
val messageParameters = message.messageParameters
|
||||
|
||||
itemView.isSelected = false
|
||||
messageTimeView!!.setTextColor(context?.resources!!.getColor(R.color.warm_grey_four))
|
||||
binding.messageTime.setTextColor(context?.resources!!.getColor(R.color.warm_grey_four))
|
||||
|
||||
var messageString: Spannable = SpannableString(message.text)
|
||||
|
||||
@ -187,7 +145,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
) {
|
||||
if (individualHashMap["id"] == message.activeUser!!.userId) {
|
||||
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
binding.messageText.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
@ -197,7 +155,7 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
)
|
||||
} else {
|
||||
messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
binding.messageText.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
@ -217,43 +175,43 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
|
||||
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
||||
textSize = (textSize * 2.5).toFloat()
|
||||
itemView.isSelected = true
|
||||
messageAuthor!!.visibility = View.GONE
|
||||
binding.messageAuthor.visibility = View.GONE
|
||||
}
|
||||
|
||||
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
messageText!!.text = messageString
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageText.text = messageString
|
||||
|
||||
// parent message handling
|
||||
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
var parentChatMessage = message.parentMessage
|
||||
val parentChatMessage = message.parentMessage
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
quotedMessagePreview?.load(it) {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedMessageImage.load(it) {
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
binding.messageQuote.quotedMessage.text = parentChatMessage.text
|
||||
|
||||
quotedUserName?.setTextColor(context!!.resources.getColor(R.color.textColorMaxContrast))
|
||||
binding.messageQuote.quotedMessageAuthor
|
||||
.setTextColor(context!!.resources.getColor(R.color.textColorMaxContrast))
|
||||
|
||||
if (parentChatMessage.actorId?.equals(message.activeUser.userId) == true) {
|
||||
quoteColoredView?.setBackgroundResource(R.color.colorPrimary)
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.colorPrimary)
|
||||
} else {
|
||||
quoteColoredView?.setBackgroundResource(R.color.textColorMaxContrast)
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
|
||||
}
|
||||
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
quotedChatMessageView?.visibility = View.GONE
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
||||
|
@ -2,6 +2,8 @@
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -27,19 +29,14 @@ import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import autodagger.AutoInjector
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import coil.load
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
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
|
||||
@ -53,57 +50,21 @@ import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewHolder<ChatMessage>(itemView) {
|
||||
@JvmField
|
||||
@BindView(R.id.messageText)
|
||||
var messageText: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.messageTime)
|
||||
var messageTimeView: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedChatMessageView)
|
||||
var quotedChatMessageView: RelativeLayout? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageAuthor)
|
||||
var quotedUserName: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessageImage)
|
||||
var quotedMessagePreview: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quotedMessage)
|
||||
var quotedMessage: EmojiTextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.quoteColoredView)
|
||||
var quoteColoredView: View? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.checkMark)
|
||||
var checkMark: ImageView? = null
|
||||
private val binding: ItemCustomOutcomingTextMessageBinding = ItemCustomOutcomingTextMessageBinding.bind(itemView)
|
||||
private val realView: View = itemView
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var context: Context? = null
|
||||
|
||||
private val realView: View
|
||||
|
||||
init {
|
||||
ButterKnife.bind(this, itemView)
|
||||
this.realView = itemView
|
||||
}
|
||||
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
val messageParameters: HashMap<String, HashMap<String, String>>? = message.messageParameters
|
||||
var messageString: Spannable = SpannableString(message.text)
|
||||
realView.isSelected = false
|
||||
messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.white60))
|
||||
val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams
|
||||
binding.messageTime.setTextColor(context!!.resources.getColor(R.color.white60))
|
||||
val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams
|
||||
layoutParams.isWrapBefore = false
|
||||
var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
|
||||
if (messageParameters != null && messageParameters.size > 0) {
|
||||
@ -115,7 +76,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
) || individualHashMap["type"] == "call"
|
||||
) {
|
||||
messageString = searchAndReplaceWithMentionSpan(
|
||||
messageText!!.context,
|
||||
binding.messageText.context,
|
||||
messageString,
|
||||
individualHashMap["id"]!!,
|
||||
individualHashMap["name"]!!,
|
||||
@ -136,7 +97,7 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
||||
textSize = (textSize * 2.5).toFloat()
|
||||
layoutParams.isWrapBefore = true
|
||||
messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.warm_grey_four))
|
||||
binding.messageTime.setTextColor(context!!.resources.getColor(R.color.warm_grey_four))
|
||||
realView.isSelected = true
|
||||
}
|
||||
val resources = sharedApplication!!.resources
|
||||
@ -162,9 +123,9 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
}
|
||||
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
messageTimeView!!.layoutParams = layoutParams
|
||||
messageText!!.text = messageString
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageTime.layoutParams = layoutParams
|
||||
binding.messageText.text = messageString
|
||||
|
||||
// parent message handling
|
||||
|
||||
@ -172,27 +133,29 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
var parentChatMessage = message.parentMessage
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
quotedMessagePreview?.visibility = View.VISIBLE
|
||||
quotedMessagePreview?.load(it) {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedMessageImage.load(it) {
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
quotedMessagePreview?.visibility = View.GONE
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||
}
|
||||
quotedUserName?.text = parentChatMessage.actorDisplayName
|
||||
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
|
||||
?: context!!.getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.text = parentChatMessage.text
|
||||
quotedMessage?.setTextColor(context!!.resources.getColor(R.color.nc_outcoming_text_default))
|
||||
quotedUserName?.setTextColor(context!!.resources.getColor(R.color.nc_grey))
|
||||
binding.messageQuote.quotedMessage.text = parentChatMessage.text
|
||||
binding.messageQuote.quotedMessage.setTextColor(
|
||||
context!!.resources.getColor(R.color.nc_outcoming_text_default)
|
||||
)
|
||||
binding.messageQuote.quotedMessageAuthor.setTextColor(context!!.resources.getColor(R.color.nc_grey))
|
||||
|
||||
quoteColoredView?.setBackgroundResource(R.color.white)
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white)
|
||||
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
quotedChatMessageView?.visibility = View.GONE
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val readStatusDrawableInt = when (message.readStatus) {
|
||||
@ -210,11 +173,11 @@ class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessage
|
||||
readStatusDrawableInt?.let { drawableInt ->
|
||||
context?.resources?.getDrawable(drawableInt, null)?.let {
|
||||
it.setColorFilter(context?.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP)
|
||||
checkMark?.setImageDrawable(it)
|
||||
binding.checkMark.setImageDrawable(it)
|
||||
}
|
||||
}
|
||||
|
||||
checkMark?.setContentDescription(readStatusContentDescriptionString)
|
||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||
|
||||
itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
|
||||
}
|
||||
|
@ -3,8 +3,10 @@
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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
|
||||
@ -35,6 +37,7 @@ import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.nextcloud.talk.R;
|
||||
@ -70,8 +73,6 @@ import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import autodagger.AutoInjector;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleObserver;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
@ -82,15 +83,10 @@ import okhttp3.OkHttpClient;
|
||||
import static com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback.REPLYABLE_VIEW_TAG;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
|
||||
public abstract class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
|
||||
|
||||
private static final String TAG = "PreviewMsgViewHolder";
|
||||
|
||||
@BindView(R.id.messageText)
|
||||
EmojiTextView messageText;
|
||||
|
||||
View progressBar;
|
||||
|
||||
@Inject
|
||||
Context context;
|
||||
|
||||
@ -99,8 +95,6 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
|
||||
public MagicPreviewMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
progressBar = itemView.findViewById(R.id.progress_bar);
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||
}
|
||||
|
||||
@ -131,7 +125,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
|
||||
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
String fileName = message.getSelectedIndividualHashMap().get("name");
|
||||
messageText.setText(fileName);
|
||||
getMessageText().setText(fileName);
|
||||
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
int drawableResourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(mimetype);
|
||||
@ -165,7 +159,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
try {
|
||||
for (WorkInfo workInfo : workers.get()) {
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING || workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
getProgressBar().setVisibility(View.VISIBLE);
|
||||
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
|
||||
@ -177,13 +171,12 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e);
|
||||
}
|
||||
|
||||
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||
messageText.setText("GIPHY");
|
||||
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", messageText);
|
||||
getMessageText().setText("GIPHY");
|
||||
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", getMessageText());
|
||||
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE) {
|
||||
messageText.setText("Tenor");
|
||||
DisplayUtils.setClickableString("Tenor", "https://tenor.com", messageText);
|
||||
getMessageText().setText("Tenor");
|
||||
DisplayUtils.setClickableString("Tenor", "https://tenor.com", getMessageText());
|
||||
} else {
|
||||
if (message.getMessageType().equals(ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
|
||||
image.setOnClickListener(v -> {
|
||||
@ -194,12 +187,16 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
} else {
|
||||
image.setOnClickListener(null);
|
||||
}
|
||||
messageText.setText("");
|
||||
getMessageText().setText("");
|
||||
}
|
||||
|
||||
itemView.setTag(REPLYABLE_VIEW_TAG, message.isReplyable());
|
||||
}
|
||||
|
||||
public abstract EmojiTextView getMessageText();
|
||||
|
||||
public abstract ProgressBar getProgressBar();
|
||||
|
||||
private void openOrDownloadFile(ChatMessage message) {
|
||||
String filename = message.getSelectedIndividualHashMap().get("name");
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
@ -389,7 +386,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker);
|
||||
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
getProgressBar().setVisibility(View.VISIBLE);
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.getId()).observeForever(workInfo -> {
|
||||
updateViewsByProgress(fileName, mimetype, workInfo);
|
||||
@ -401,7 +398,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
case RUNNING:
|
||||
int progress = workInfo.getProgress().getInt(DownloadFileToCacheWorker.PROGRESS, -1);
|
||||
if (progress > -1) {
|
||||
messageText.setText(String.format(context.getResources().getString(R.string.filename_progress), fileName, progress));
|
||||
getMessageText().setText(String.format(context.getResources().getString(R.string.filename_progress), fileName, progress));
|
||||
}
|
||||
break;
|
||||
|
||||
@ -412,13 +409,13 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
Log.d(TAG, "file " + fileName + " was downloaded but it's not opened because view is not shown on" +
|
||||
" screen");
|
||||
}
|
||||
messageText.setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
getMessageText().setText(fileName);
|
||||
getProgressBar().setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
messageText.setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
getMessageText().setText(fileName);
|
||||
getProgressBar().setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
@ -487,6 +484,5 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
||||
Log.e(TAG, "Error reading file information", e);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.ViewCompat
|
||||
import autodagger.AutoInjector
|
||||
import coil.load
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
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.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import java.net.URLEncoder
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
|
||||
.OutcomingTextMessageViewHolder<ChatMessage>(incomingView) {
|
||||
private val binding: ItemCustomOutcomingLocationMessageBinding =
|
||||
ItemCustomOutcomingLocationMessageBinding.bind(itemView)
|
||||
private val realView: View = itemView
|
||||
|
||||
var locationLon: String? = ""
|
||||
var locationLat: String? = ""
|
||||
var locationName: String? = ""
|
||||
var locationGeoLink: String? = ""
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var context: Context? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBind(message: ChatMessage) {
|
||||
super.onBind(message)
|
||||
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
|
||||
|
||||
val textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
|
||||
|
||||
colorizeMessageBubble(message)
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageTime.layoutParams = layoutParams
|
||||
binding.messageText.text = message.text
|
||||
binding.messageText.isEnabled = false
|
||||
|
||||
// parent message handling
|
||||
setParentMessageDataOnMessageItem(message)
|
||||
|
||||
val readStatusDrawableInt = when (message.readStatus) {
|
||||
ReadStatus.READ -> R.drawable.ic_check_all
|
||||
ReadStatus.SENT -> R.drawable.ic_check
|
||||
else -> null
|
||||
}
|
||||
|
||||
val readStatusContentDescriptionString = when (message.readStatus) {
|
||||
ReadStatus.READ -> context?.resources?.getString(R.string.nc_message_read)
|
||||
ReadStatus.SENT -> context?.resources?.getString(R.string.nc_message_sent)
|
||||
else -> null
|
||||
}
|
||||
|
||||
readStatusDrawableInt?.let { drawableInt ->
|
||||
AppCompatResources.getDrawable(context!!, drawableInt)?.let {
|
||||
it.setColorFilter(context?.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP)
|
||||
binding.checkMark.setImageDrawable(it)
|
||||
}
|
||||
}
|
||||
|
||||
binding.checkMark.setContentDescription(readStatusContentDescriptionString)
|
||||
|
||||
// geo-location
|
||||
setLocationDataOnMessageItem(message)
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
|
||||
private fun setLocationDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.messageParameters != null && message.messageParameters.size > 0) {
|
||||
for (key in message.messageParameters.keys) {
|
||||
val individualHashMap: Map<String, String> = message.messageParameters[key]!!
|
||||
if (individualHashMap["type"] == "geo-location") {
|
||||
locationLon = individualHashMap["longitude"]
|
||||
locationLat = individualHashMap["latitude"]
|
||||
locationName = individualHashMap["name"]
|
||||
locationGeoLink = individualHashMap["id"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.webview.settings?.javaScriptEnabled = true
|
||||
|
||||
binding.webview.webViewClient = object : WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
|
||||
return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))
|
||||
) {
|
||||
view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html")
|
||||
urlStringBuffer.append(
|
||||
"?mapProviderUrl=" + URLEncoder.encode(context!!.getString(R.string.osm_tile_server_url))
|
||||
)
|
||||
urlStringBuffer.append(
|
||||
"&mapProviderAttribution=" + URLEncoder.encode(
|
||||
context!!.getString(
|
||||
R.string
|
||||
.osm_tile_server_attributation
|
||||
)
|
||||
)
|
||||
)
|
||||
urlStringBuffer.append("&locationLat=" + URLEncoder.encode(locationLat))
|
||||
urlStringBuffer.append("&locationLon=" + URLEncoder.encode(locationLon))
|
||||
urlStringBuffer.append("&locationName=" + URLEncoder.encode(locationName))
|
||||
urlStringBuffer.append("&locationGeoLink=" + URLEncoder.encode(locationGeoLink))
|
||||
|
||||
binding.webview.loadUrl(urlStringBuffer.toString())
|
||||
|
||||
binding.webview.setOnTouchListener(object : View.OnTouchListener {
|
||||
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||
when (event?.action) {
|
||||
MotionEvent.ACTION_UP -> openGeoLink()
|
||||
}
|
||||
|
||||
return v?.onTouchEvent(event) ?: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
val parentChatMessage = message.parentMessage
|
||||
parentChatMessage.activeUser = message.activeUser
|
||||
parentChatMessage.imageUrl?.let {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
|
||||
binding.messageQuote.quotedMessageImage.load(it) {
|
||||
addHeader(
|
||||
"Authorization",
|
||||
ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
binding.messageQuote.quotedMessageImage.visibility = View.GONE
|
||||
}
|
||||
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.quotedMessageAuthor.setTextColor(context!!.resources.getColor(R.color.nc_grey))
|
||||
|
||||
binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.white)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun colorizeMessageBubble(message: ChatMessage) {
|
||||
val resources = sharedApplication!!.resources
|
||||
val bgBubbleColor = if (message.isDeleted) {
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted)
|
||||
} else {
|
||||
resources.getColor(R.color.bg_message_list_outcoming_bubble)
|
||||
}
|
||||
if (message.isGrouped) {
|
||||
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_grouped_outcoming_message
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
} else {
|
||||
val bubbleDrawable = DisplayUtils.getMessageSelector(
|
||||
bgBubbleColor,
|
||||
resources.getColor(R.color.transparent),
|
||||
bgBubbleColor,
|
||||
R.drawable.shape_outcoming_message
|
||||
)
|
||||
ViewCompat.setBackground(bubble, bubbleDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openGeoLink() {
|
||||
if (!locationGeoLink.isNullOrEmpty()) {
|
||||
val geoLinkWithMarker = addMarkerToGeoLink(locationGeoLink!!)
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(geoLinkWithMarker))
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context!!.startActivity(browserIntent)
|
||||
} else {
|
||||
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "locationGeoLink was null or empty")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMarkerToGeoLink(locationGeoLink: String): String {
|
||||
return locationGeoLink.replace("geo:", "geo:0,0?q=")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocOutMessageView"
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
||||
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
|
||||
public class OutcomingPreviewMessageViewHolder extends MagicPreviewMessageViewHolder {
|
||||
private final ItemCustomOutcomingPreviewMessageBinding binding;
|
||||
|
||||
public OutcomingPreviewMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
binding = ItemCustomOutcomingPreviewMessageBinding.bind(itemView);
|
||||
}
|
||||
|
||||
public EmojiTextView getMessageText() {
|
||||
return binding.messageText;
|
||||
}
|
||||
|
||||
public ProgressBar getProgressBar() {
|
||||
return binding.progressBar;
|
||||
}
|
||||
}
|
@ -406,4 +406,12 @@ public interface NcApi {
|
||||
@GET
|
||||
Call<ResponseBody> downloadResizedImage(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
Observable<GenericOverall> sendLocation(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("objectType") String objectType,
|
||||
@Field("objectId") String objectId,
|
||||
@Field("metaData") String metaData);
|
||||
}
|
||||
|
@ -78,11 +78,14 @@ import com.facebook.imagepipeline.image.CloseableImage
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MagicCallActivity
|
||||
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.MagicIncomingTextMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.MagicPreviewMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.OutcomingPreviewMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
@ -133,6 +136,7 @@ import com.otaliastudios.autocomplete.Autocomplete
|
||||
import com.stfalcon.chatkit.commons.ImageLoader
|
||||
import com.stfalcon.chatkit.commons.models.IMessage
|
||||
import com.stfalcon.chatkit.messages.MessageHolders
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.ContentChecker
|
||||
import com.stfalcon.chatkit.messages.MessagesListAdapter
|
||||
import com.stfalcon.chatkit.utils.DateFormatter
|
||||
import com.vanniktech.emoji.EmojiPopup
|
||||
@ -163,7 +167,7 @@ class ChatController(args: Bundle) :
|
||||
MessagesListAdapter.OnLoadMoreListener,
|
||||
MessagesListAdapter.Formatter<Date>,
|
||||
MessagesListAdapter.OnMessageViewLongClickListener<IMessage>,
|
||||
MessageHolders.ContentChecker<IMessage> {
|
||||
ContentChecker<ChatMessage> {
|
||||
private val binding: ControllerChatBinding by viewBinding(ControllerChatBinding::bind)
|
||||
|
||||
@Inject
|
||||
@ -397,17 +401,20 @@ class ChatController(args: Bundle) :
|
||||
)
|
||||
|
||||
messageHolders.setIncomingImageConfig(
|
||||
MagicPreviewMessageViewHolder::class.java,
|
||||
IncomingPreviewMessageViewHolder::class.java,
|
||||
R.layout.item_custom_incoming_preview_message
|
||||
)
|
||||
|
||||
messageHolders.setOutcomingImageConfig(
|
||||
MagicPreviewMessageViewHolder::class.java,
|
||||
OutcomingPreviewMessageViewHolder::class.java,
|
||||
R.layout.item_custom_outcoming_preview_message
|
||||
)
|
||||
|
||||
messageHolders.registerContentType(
|
||||
CONTENT_TYPE_SYSTEM_MESSAGE, MagicSystemMessageViewHolder::class.java,
|
||||
R.layout.item_system_message, MagicSystemMessageViewHolder::class.java,
|
||||
CONTENT_TYPE_SYSTEM_MESSAGE,
|
||||
MagicSystemMessageViewHolder::class.java,
|
||||
R.layout.item_system_message,
|
||||
MagicSystemMessageViewHolder::class.java,
|
||||
R.layout.item_system_message,
|
||||
this
|
||||
)
|
||||
@ -420,6 +427,15 @@ class ChatController(args: Bundle) :
|
||||
R.layout.item_date_header, this
|
||||
)
|
||||
|
||||
messageHolders.registerContentType(
|
||||
CONTENT_TYPE_LOCATION,
|
||||
IncomingLocationMessageViewHolder::class.java,
|
||||
R.layout.item_custom_incoming_location_message,
|
||||
OutcomingLocationMessageViewHolder::class.java,
|
||||
R.layout.item_custom_outcoming_location_message,
|
||||
this
|
||||
)
|
||||
|
||||
var senderId = ""
|
||||
if (!conversationUser?.userId.equals("?")) {
|
||||
senderId = "users/" + conversationUser?.userId
|
||||
@ -793,6 +809,18 @@ class ChatController(args: Bundle) :
|
||||
)
|
||||
}
|
||||
|
||||
fun showShareLocationScreen() {
|
||||
Log.d(TAG, "showShareLocationScreen")
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
router.pushController(
|
||||
RouterTransaction.with(LocationPickerController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
private fun showConversationInfoScreen() {
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
|
||||
@ -1861,6 +1889,8 @@ class ChatController(args: Bundle) :
|
||||
|
||||
if (message.hasFileAttachment()) return false
|
||||
|
||||
if (OBJECT_MESSAGE.equals(message.message)) return false
|
||||
|
||||
val isOlderThanSixHours = message
|
||||
.createdAt
|
||||
?.before(Date(System.currentTimeMillis() - AGE_THREHOLD_FOR_DELETE_MESSAGE)) == true
|
||||
@ -1878,8 +1908,9 @@ class ChatController(args: Bundle) :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hasContentFor(message: IMessage, type: Byte): Boolean {
|
||||
override fun hasContentFor(message: ChatMessage, type: Byte): Boolean {
|
||||
return when (type) {
|
||||
CONTENT_TYPE_LOCATION -> return message.isLocationMessage()
|
||||
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
|
||||
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == "-1"
|
||||
else -> false
|
||||
@ -1985,6 +2016,7 @@ class ChatController(args: Bundle) :
|
||||
private const val TAG = "ChatController"
|
||||
private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
|
||||
private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
|
||||
private const val CONTENT_TYPE_LOCATION: Byte = 3
|
||||
private const val NEW_MESSAGES_POPUP_BUBBLE_DELAY: Long = 200
|
||||
private const val POP_CURRENT_CONTROLLER_DELAY: Long = 100
|
||||
private const val LOBBY_TIMER_DELAY: Long = 5000
|
||||
@ -1992,5 +2024,6 @@ class ChatController(args: Bundle) :
|
||||
private const val MESSAGE_MAX_LENGTH: Int = 1000
|
||||
private const val AGE_THREHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000)
|
||||
private const val REQUEST_CODE_CHOOSE_FILE: Int = 555
|
||||
private const val OBJECT_MESSAGE: String = "{object}"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.GeocodingAdapter
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.controllers.base.NewBaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerGeocodingBinding
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import fr.dudie.nominatim.client.TalkJsonNominatimClient
|
||||
import fr.dudie.nominatim.model.Address
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.osmdroid.config.Configuration
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class GeocodingController(args: Bundle) :
|
||||
NewBaseController(
|
||||
R.layout.controller_geocoding,
|
||||
args
|
||||
),
|
||||
SearchView.OnQueryTextListener {
|
||||
private val binding: ControllerGeocodingBinding by viewBinding(ControllerGeocodingBinding::bind)
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@Inject
|
||||
lateinit var userUtils: UserUtils
|
||||
|
||||
@Inject
|
||||
lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
var roomToken: String?
|
||||
var nominatimClient: TalkJsonNominatimClient? = null
|
||||
|
||||
var searchItem: MenuItem? = null
|
||||
var searchView: SearchView? = null
|
||||
var query: String? = null
|
||||
|
||||
lateinit var adapter: GeocodingAdapter
|
||||
private var geocodingResults: List<Address> = ArrayList()
|
||||
|
||||
constructor(args: Bundle, listener: LocationPickerController) : this(args) {
|
||||
targetController = listener
|
||||
}
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
query = args.getString(BundleKeys.KEY_GEOCODING_QUERY)
|
||||
roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN)
|
||||
}
|
||||
|
||||
private fun initAdapter(addresses: List<Address>) {
|
||||
adapter = GeocodingAdapter(binding.geocodingResults.context!!, addresses)
|
||||
binding.geocodingResults.adapter = adapter
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
|
||||
initAdapter(geocodingResults)
|
||||
|
||||
initGeocoder()
|
||||
if (!query.isNullOrEmpty()) {
|
||||
searchLocation()
|
||||
} else {
|
||||
Log.e(TAG, "search string that was passed to GeocodingController was null or empty")
|
||||
}
|
||||
|
||||
binding.geocodingResults.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
|
||||
val address: Address = adapter.getItem(position) as Address
|
||||
val listener: GeocodingResultListener? = targetController as GeocodingResultListener?
|
||||
listener?.receiveChosenGeocodingResult(address.latitude, address.longitude, address.displayName)
|
||||
router.popCurrentController()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.menu_geocoding, menu)
|
||||
searchItem = menu.findItem(R.id.geocoding_action_search)
|
||||
initSearchView()
|
||||
|
||||
searchItem?.expandActionView()
|
||||
searchView?.setQuery(query, false)
|
||||
searchView?.clearFocus()
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
this.query = query
|
||||
searchLocation()
|
||||
searchView?.clearFocus()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
if (activity != null) {
|
||||
val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
if (searchItem != null) {
|
||||
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
|
||||
searchView?.maxWidth = Int.MAX_VALUE
|
||||
searchView?.inputType = InputType.TYPE_TEXT_VARIATION_FILTER
|
||||
var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) {
|
||||
imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
}
|
||||
searchView?.imeOptions = imeOptions
|
||||
searchView?.queryHint = resources!!.getString(R.string.nc_search)
|
||||
searchView?.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
|
||||
searchView?.setOnQueryTextListener(this)
|
||||
|
||||
searchItem?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||
override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean {
|
||||
router.popCurrentController()
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initGeocoder() {
|
||||
val baseUrl = context!!.getString(R.string.osm_geocoder_url)
|
||||
val email = context!!.getString(R.string.osm_geocoder_contact)
|
||||
nominatimClient = TalkJsonNominatimClient(baseUrl, okHttpClient, email)
|
||||
}
|
||||
|
||||
private fun searchLocation(): Boolean {
|
||||
CoroutineScope(IO).launch {
|
||||
executeGeocodingRequest()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private suspend fun executeGeocodingRequest() {
|
||||
var results: ArrayList<Address> = ArrayList()
|
||||
try {
|
||||
results = nominatimClient!!.search(query) as ArrayList<Address>
|
||||
for (address in results) {
|
||||
Log.d(TAG, address.displayName)
|
||||
Log.d(TAG, address.latitude.toString())
|
||||
Log.d(TAG, address.longitude.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get geocoded addresses", e)
|
||||
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
updateResultsOnMainThread(results)
|
||||
}
|
||||
|
||||
private suspend fun updateResultsOnMainThread(results: ArrayList<Address>) {
|
||||
withContext(Main) {
|
||||
initAdapter(results)
|
||||
}
|
||||
}
|
||||
|
||||
interface GeocodingResultListener {
|
||||
fun receiveChosenGeocodingResult(lat: Double, lon: Double, name: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeocodingController"
|
||||
}
|
||||
}
|
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers
|
||||
|
||||
import android.Manifest
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.PermissionChecker
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.controllers.base.NewBaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerLocationBinding
|
||||
import com.nextcloud.talk.models.json.generic.GenericOverall
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import fr.dudie.nominatim.client.TalkJsonNominatimClient
|
||||
import fr.dudie.nominatim.model.Address
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.osmdroid.config.Configuration.getInstance
|
||||
import org.osmdroid.events.DelayedMapListener
|
||||
import org.osmdroid.events.MapListener
|
||||
import org.osmdroid.events.ScrollEvent
|
||||
import org.osmdroid.events.ZoomEvent
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.overlay.CopyrightOverlay
|
||||
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider
|
||||
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class LocationPickerController(args: Bundle) :
|
||||
NewBaseController(
|
||||
R.layout.controller_location,
|
||||
args
|
||||
),
|
||||
SearchView.OnQueryTextListener,
|
||||
LocationListener,
|
||||
GeocodingController.GeocodingResultListener {
|
||||
private val binding: ControllerLocationBinding by viewBinding(ControllerLocationBinding::bind)
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@Inject
|
||||
lateinit var userUtils: UserUtils
|
||||
|
||||
@Inject
|
||||
lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
var nominatimClient: TalkJsonNominatimClient? = null
|
||||
|
||||
var roomToken: String?
|
||||
|
||||
var myLocation: GeoPoint = GeoPoint(0.0, 0.0)
|
||||
private var locationManager: LocationManager? = null
|
||||
private lateinit var locationOverlay: MyLocationNewOverlay
|
||||
|
||||
var moveToCurrentLocationWasClicked: Boolean = true
|
||||
var readyToShareLocation: Boolean = false
|
||||
var searchItem: MenuItem? = null
|
||||
var searchView: SearchView? = null
|
||||
|
||||
var receivedChosenGeocodingResult: Boolean = false
|
||||
var geocodedLat: Double = 0.0
|
||||
var geocodedLon: Double = 0.0
|
||||
var geocodedName: String = ""
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
|
||||
roomToken = args.getString(KEY_ROOM_TOKEN)
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
initMap()
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
try {
|
||||
locationManager!!.removeUpdates(this)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "error when trying to remove updates for location Manager", e)
|
||||
}
|
||||
locationOverlay.disableMyLocation()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.menu_locationpicker, menu)
|
||||
searchItem = menu.findItem(R.id.location_action_search)
|
||||
initSearchView()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
actionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent)))
|
||||
actionBar?.title = context!!.getString(R.string.nc_share_location)
|
||||
}
|
||||
|
||||
override val title: String
|
||||
get() =
|
||||
resources!!.getString(R.string.nc_share_location)
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
setLocationDescription(false, receivedChosenGeocodingResult)
|
||||
binding.shareLocation.isClickable = false
|
||||
binding.shareLocation.setOnClickListener {
|
||||
if (readyToShareLocation) {
|
||||
shareLocation(
|
||||
binding.map.mapCenter?.latitude,
|
||||
binding.map.mapCenter?.longitude,
|
||||
binding.placeName.text.toString()
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "readyToShareLocation was false while user tried to share location.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
if (activity != null) {
|
||||
val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
if (searchItem != null) {
|
||||
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
|
||||
searchView?.maxWidth = Int.MAX_VALUE
|
||||
searchView?.inputType = InputType.TYPE_TEXT_VARIATION_FILTER
|
||||
var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) {
|
||||
imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
}
|
||||
searchView?.imeOptions = imeOptions
|
||||
searchView?.queryHint = resources!!.getString(R.string.nc_search)
|
||||
searchView?.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
|
||||
searchView?.setOnQueryTextListener(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_GEOCODING_QUERY, query)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
router.pushController(
|
||||
RouterTransaction.with(GeocodingController(bundle, this))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun initMap() {
|
||||
if (!isLocationPermissionsGranted()) {
|
||||
requestLocationPermissions()
|
||||
}
|
||||
|
||||
binding.map.setTileSource(TileSourceFactory.MAPNIK)
|
||||
|
||||
binding.map.onResume()
|
||||
|
||||
locationManager = activity!!.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
try {
|
||||
locationManager!!.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, this)
|
||||
} catch (ex: SecurityException) {
|
||||
Log.w(TAG, "Error requesting location updates", ex)
|
||||
}
|
||||
|
||||
val copyrightOverlay = CopyrightOverlay(context)
|
||||
binding.map.overlays?.add(copyrightOverlay)
|
||||
|
||||
binding.map.setMultiTouchControls(true)
|
||||
binding.map.isTilesScaledToDpi = true
|
||||
|
||||
locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(context), binding.map)
|
||||
locationOverlay.enableMyLocation()
|
||||
locationOverlay.setPersonHotspot(PERSON_HOT_SPOT_X, PERSON_HOT_SPOT_Y)
|
||||
locationOverlay.setPersonIcon(
|
||||
DisplayUtils.getBitmap(
|
||||
ResourcesCompat.getDrawable(resources!!, R.drawable.current_location_circle, null)
|
||||
)
|
||||
)
|
||||
binding.map.overlays?.add(locationOverlay)
|
||||
|
||||
val mapController = binding.map.controller
|
||||
|
||||
if (receivedChosenGeocodingResult) {
|
||||
mapController?.setZoom(ZOOM_LEVEL_RECEIVED_RESULT)
|
||||
} else {
|
||||
mapController?.setZoom(ZOOM_LEVEL_DEFAULT)
|
||||
}
|
||||
|
||||
val zoomToCurrentPositionOnFirstFix = !receivedChosenGeocodingResult
|
||||
locationOverlay.runOnFirstFix {
|
||||
myLocation = locationOverlay.myLocation
|
||||
if (zoomToCurrentPositionOnFirstFix) {
|
||||
activity!!.runOnUiThread {
|
||||
mapController?.setZoom(ZOOM_LEVEL_DEFAULT)
|
||||
mapController?.setCenter(myLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (receivedChosenGeocodingResult && geocodedLat != GEOCODE_ZERO && geocodedLon != GEOCODE_ZERO) {
|
||||
mapController?.setCenter(GeoPoint(geocodedLat, geocodedLon))
|
||||
}
|
||||
|
||||
binding.centerMapButton.setOnClickListener {
|
||||
mapController?.animateTo(myLocation)
|
||||
moveToCurrentLocationWasClicked = true
|
||||
}
|
||||
|
||||
binding.map.addMapListener(
|
||||
DelayedMapListener(
|
||||
object : MapListener {
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onScroll(paramScrollEvent: ScrollEvent): Boolean {
|
||||
try {
|
||||
when {
|
||||
moveToCurrentLocationWasClicked -> {
|
||||
setLocationDescription(isGpsLocation = true, isGeocodedResult = false)
|
||||
moveToCurrentLocationWasClicked = false
|
||||
}
|
||||
receivedChosenGeocodingResult -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = true)
|
||||
receivedChosenGeocodingResult = false
|
||||
}
|
||||
else -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = false)
|
||||
}
|
||||
}
|
||||
} catch (e: NullPointerException) {
|
||||
Log.d(TAG, "UI already closed")
|
||||
}
|
||||
|
||||
readyToShareLocation = true
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onZoom(event: ZoomEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun setLocationDescription(isGpsLocation: Boolean, isGeocodedResult: Boolean) {
|
||||
when {
|
||||
isGpsLocation -> {
|
||||
binding.shareLocationDescription.text = context!!.getText(R.string.nc_share_current_location)
|
||||
binding.placeName.visibility = View.GONE
|
||||
binding.placeName.text = ""
|
||||
}
|
||||
isGeocodedResult -> {
|
||||
binding.shareLocationDescription.text = context!!.getText(R.string.nc_share_this_location)
|
||||
binding.placeName.visibility = View.VISIBLE
|
||||
binding.placeName.text = geocodedName
|
||||
}
|
||||
else -> {
|
||||
binding.shareLocationDescription.text = context!!.getText(R.string.nc_share_this_location)
|
||||
binding.placeName.visibility = View.GONE
|
||||
binding.placeName.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareLocation(selectedLat: Double?, selectedLon: Double?, locationName: String?) {
|
||||
if (selectedLat != null || selectedLon != null) {
|
||||
|
||||
val name = locationName
|
||||
if (name.isNullOrEmpty()) {
|
||||
initGeocoder()
|
||||
searchPlaceNameForCoordinates(selectedLat!!, selectedLon!!)
|
||||
} else {
|
||||
executeShareLocation(selectedLat, selectedLon, locationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeShareLocation(selectedLat: Double?, selectedLon: Double?, locationName: String?) {
|
||||
val objectId = "geo:$selectedLat,$selectedLon"
|
||||
val metaData: String =
|
||||
"{\"type\":\"geo-location\",\"id\":\"geo:$selectedLat,$selectedLon\",\"latitude\":\"$selectedLat\"," +
|
||||
"\"longitude\":\"$selectedLon\",\"name\":\"$locationName\"}"
|
||||
|
||||
ncApi.sendLocation(
|
||||
ApiUtils.getCredentials(userUtils.currentUser?.username, userUtils.currentUser?.token),
|
||||
ApiUtils.getUrlToSendLocation(userUtils.currentUser?.baseUrl, roomToken),
|
||||
"geo-location",
|
||||
objectId,
|
||||
metaData
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<GenericOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(t: GenericOverall) {
|
||||
router.popCurrentController()
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "error when trying to share location", e)
|
||||
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||
router.popCurrentController()
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun isLocationPermissionsGranted(): Boolean {
|
||||
fun isCoarseLocationGranted(): Boolean {
|
||||
return PermissionChecker.checkSelfPermission(
|
||||
context!!,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
) == PermissionChecker.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
fun isFineLocationGranted(): Boolean {
|
||||
return PermissionChecker.checkSelfPermission(
|
||||
context!!,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PermissionChecker.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
isCoarseLocationGranted() && isFineLocationGranted()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestLocationPermissions() {
|
||||
requestPermissions(
|
||||
arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
),
|
||||
REQUEST_PERMISSIONS_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE &&
|
||||
grantResults.size > 0 &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
initMap()
|
||||
} else {
|
||||
Toast.makeText(context, context!!.getString(R.string.nc_location_permission_required), Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun receiveChosenGeocodingResult(lat: Double, lon: Double, name: String) {
|
||||
receivedChosenGeocodingResult = true
|
||||
geocodedLat = lat
|
||||
geocodedLon = lon
|
||||
geocodedName = name
|
||||
}
|
||||
|
||||
private fun initGeocoder() {
|
||||
val baseUrl = context!!.getString(R.string.osm_geocoder_url)
|
||||
val email = context!!.getString(R.string.osm_geocoder_contact)
|
||||
nominatimClient = TalkJsonNominatimClient(baseUrl, okHttpClient, email)
|
||||
}
|
||||
|
||||
private fun searchPlaceNameForCoordinates(lat: Double, lon: Double): Boolean {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
executeGeocodingRequest(lat, lon)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private suspend fun executeGeocodingRequest(lat: Double, lon: Double) {
|
||||
var address: Address? = null
|
||||
try {
|
||||
address = nominatimClient!!.getAddress(lon, lat)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get geocoded addresses", e)
|
||||
Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
updateResultOnMainThread(lat, lon, address?.displayName)
|
||||
}
|
||||
|
||||
private suspend fun updateResultOnMainThread(lat: Double, lon: Double, addressName: String?) {
|
||||
withContext(Dispatchers.Main) {
|
||||
executeShareLocation(lat, lon, addressName)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location?) {
|
||||
myLocation = GeoPoint(location)
|
||||
}
|
||||
|
||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
|
||||
// empty
|
||||
}
|
||||
|
||||
override fun onProviderEnabled(provider: String?) {
|
||||
// empty
|
||||
}
|
||||
|
||||
override fun onProviderDisabled(provider: String?) {
|
||||
// empty
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LocPicker"
|
||||
private const val REQUEST_PERMISSIONS_REQUEST_CODE = 1
|
||||
private const val PERSON_HOT_SPOT_X: Float = 20.0F
|
||||
private const val PERSON_HOT_SPOT_Y: Float = 20.0F
|
||||
private const val ZOOM_LEVEL_RECEIVED_RESULT: Double = 14.0
|
||||
private const val ZOOM_LEVEL_DEFAULT: Double = 14.0
|
||||
private const val GEOCODE_ZERO: Double = 0.0
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.interfaces
|
||||
|
||||
import com.stfalcon.chatkit.commons.models.IMessage
|
||||
|
||||
interface ExtendedIMessage : IMessage {
|
||||
|
||||
fun isLocationMessage(): Boolean
|
||||
}
|
@ -26,27 +26,30 @@ import com.bluelinelabs.logansquare.annotation.JsonIgnore;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.interfaces.ExtendedIMessage;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
import com.nextcloud.talk.utils.TextMatchers;
|
||||
import com.stfalcon.chatkit.commons.models.IMessage;
|
||||
import com.stfalcon.chatkit.commons.models.IUser;
|
||||
import com.stfalcon.chatkit.commons.models.MessageContentType;
|
||||
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import kotlin.text.Charsets;
|
||||
|
||||
@Parcel
|
||||
@JsonObject
|
||||
public class ChatMessage implements IMessage, MessageContentType, MessageContentType.Image {
|
||||
public class ChatMessage implements ExtendedIMessage, MessageContentType, MessageContentType.Image {
|
||||
@JsonIgnore
|
||||
public boolean isGrouped;
|
||||
@JsonIgnore
|
||||
@ -87,15 +90,21 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
public Enum<ReadStatus> readStatus = ReadStatus.NONE;
|
||||
|
||||
@JsonIgnore
|
||||
List<MessageType> messageTypesToIgnore = Arrays.asList(MessageType.REGULAR_TEXT_MESSAGE,
|
||||
MessageType.SYSTEM_MESSAGE, MessageType.SINGLE_LINK_VIDEO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_AUDIO_MESSAGE, MessageType.SINGLE_LINK_MESSAGE);
|
||||
List<MessageType> messageTypesToIgnore = Arrays.asList(
|
||||
MessageType.REGULAR_TEXT_MESSAGE,
|
||||
MessageType.SYSTEM_MESSAGE,
|
||||
MessageType.SINGLE_LINK_VIDEO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_AUDIO_MESSAGE,
|
||||
MessageType.SINGLE_LINK_MESSAGE,
|
||||
MessageType.SINGLE_NC_GEOLOCATION_MESSAGE);
|
||||
|
||||
public boolean hasFileAttachment() {
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (String key : messageParameters.keySet()) {
|
||||
Map<String, String> individualHashMap = messageParameters.get(key);
|
||||
if (individualHashMap.get("type").equals("file")) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -103,13 +112,31 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasGeoLocation() {
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("geo-location").getBytes(Charsets.UTF_8))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getImageUrl() {
|
||||
if (messageParameters != null && messageParameters.size() > 0) {
|
||||
for (String key : messageParameters.keySet()) {
|
||||
Map<String, String> individualHashMap = messageParameters.get(key);
|
||||
if (individualHashMap.get("type").equals("file")) {
|
||||
for (HashMap.Entry<String, HashMap<String, String>> entry : messageParameters.entrySet()) {
|
||||
Map<String, String> individualHashMap = entry.getValue();
|
||||
if(MessageDigest.isEqual(
|
||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||
("file").getBytes(Charsets.UTF_8))) {
|
||||
selectedIndividualHashMap = individualHashMap;
|
||||
return (ApiUtils.getUrlForFilePreviewWithFileId(getActiveUser().getBaseUrl(),
|
||||
individualHashMap.get("id"), NextcloudTalkApplication.Companion.getSharedApplication().getResources().getDimensionPixelSize(R.dimen.maximum_file_preview_size)));
|
||||
@ -133,6 +160,11 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
return MessageType.SINGLE_NC_ATTACHMENT_MESSAGE;
|
||||
}
|
||||
|
||||
if (hasGeoLocation()) {
|
||||
return MessageType.SINGLE_NC_GEOLOCATION_MESSAGE;
|
||||
}
|
||||
|
||||
|
||||
return TextMatchers.getMessageTypeFromString(getText());
|
||||
}
|
||||
|
||||
@ -158,22 +190,29 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
if (getMessageType().equals(MessageType.REGULAR_TEXT_MESSAGE) || getMessageType().equals(MessageType.SYSTEM_MESSAGE) || getMessageType().equals(MessageType.SINGLE_LINK_MESSAGE)) {
|
||||
return getText();
|
||||
} else {
|
||||
if (getMessageType().equals(MessageType.SINGLE_LINK_GIPHY_MESSAGE)
|
||||
|| getMessageType().equals(MessageType.SINGLE_LINK_TENOR_MESSAGE)
|
||||
|| getMessageType().equals(MessageType.SINGLE_LINK_GIF_MESSAGE)) {
|
||||
if (MessageType.SINGLE_LINK_GIPHY_MESSAGE == getMessageType()
|
||||
|| MessageType.SINGLE_LINK_TENOR_MESSAGE == getMessageType()
|
||||
|| MessageType.SINGLE_LINK_GIF_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_a_gif_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_a_gif),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (getMessageType().equals(MessageType.SINGLE_NC_ATTACHMENT_MESSAGE)) {
|
||||
} else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_attachment_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_an_attachment),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (MessageType.SINGLE_NC_GEOLOCATION_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_location_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_location),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
/*} else if (getMessageType().equals(MessageType.SINGLE_LINK_MESSAGE)) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_a_link_you));
|
||||
@ -181,21 +220,21 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_a_link),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}*/
|
||||
} else if (getMessageType().equals(MessageType.SINGLE_LINK_AUDIO_MESSAGE)) {
|
||||
} else if (MessageType.SINGLE_LINK_AUDIO_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_audio_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_an_audio),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (getMessageType().equals(MessageType.SINGLE_LINK_VIDEO_MESSAGE)) {
|
||||
} else if (MessageType.SINGLE_LINK_VIDEO_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_a_video_you));
|
||||
} else {
|
||||
return (String.format(NextcloudTalkApplication.Companion.getSharedApplication().getResources().getString(R.string.nc_sent_a_video),
|
||||
!TextUtils.isEmpty(getActorDisplayName()) ? getActorDisplayName() : NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_guest)));
|
||||
}
|
||||
} else if (getMessageType().equals(MessageType.SINGLE_LINK_IMAGE_MESSAGE)) {
|
||||
} else if (MessageType.SINGLE_LINK_IMAGE_MESSAGE == getMessageType()) {
|
||||
if (getActorId().equals(getActiveUser().getUserId())) {
|
||||
return (NextcloudTalkApplication.Companion.getSharedApplication().getString(R.string.nc_sent_an_image_you));
|
||||
} else {
|
||||
@ -537,6 +576,11 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
return "ChatMessage(isGrouped=" + this.isGrouped() + ", isOneToOneConversation=" + this.isOneToOneConversation() + ", activeUser=" + this.getActiveUser() + ", selectedIndividualHashMap=" + this.getSelectedIndividualHashMap() + ", isLinkPreviewAllowed=" + this.isLinkPreviewAllowed() + ", isDeleted=" + this.isDeleted() + ", jsonMessageId=" + this.getJsonMessageId() + ", token=" + this.getToken() + ", actorType=" + this.getActorType() + ", actorId=" + this.getActorId() + ", actorDisplayName=" + this.getActorDisplayName() + ", timestamp=" + this.getTimestamp() + ", message=" + this.getMessage() + ", messageParameters=" + this.getMessageParameters() + ", systemMessageType=" + this.getSystemMessageType() + ", replyable=" + this.isReplyable() + ", parentMessage=" + this.getParentMessage() + ", readStatus=" + this.getReadStatus() + ", messageTypesToIgnore=" + this.getMessageTypesToIgnore() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocationMessage() {
|
||||
return hasGeoLocation();
|
||||
}
|
||||
|
||||
public enum MessageType {
|
||||
REGULAR_TEXT_MESSAGE,
|
||||
SYSTEM_MESSAGE,
|
||||
@ -548,6 +592,7 @@ public class ChatMessage implements IMessage, MessageContentType, MessageContent
|
||||
SINGLE_LINK_IMAGE_MESSAGE,
|
||||
SINGLE_LINK_AUDIO_MESSAGE,
|
||||
SINGLE_NC_ATTACHMENT_MESSAGE,
|
||||
SINGLE_NC_GEOLOCATION_MESSAGE,
|
||||
}
|
||||
|
||||
public enum SystemMessageType {
|
||||
|
@ -33,6 +33,8 @@ class ChatUtils {
|
||||
val type = individualHashMap?.get("type")
|
||||
if (type == "user" || type == "guest" || type == "call") {
|
||||
resultMessage = resultMessage?.replace("{$key}", "@" + individualHashMap["name"])
|
||||
} else if (type == "geo-location") {
|
||||
resultMessage = individualHashMap.get("name")
|
||||
} else if (individualHashMap?.containsKey("link") == true) {
|
||||
resultMessage = if (type == "file") {
|
||||
resultMessage?.replace("{$key}", individualHashMap["name"].toString())
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2021 Marcel Hibbe <marcel.hibbe@nextcloud.com>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* 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
|
||||
@ -22,7 +22,9 @@ package com.nextcloud.talk.ui.dialog
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
@ -35,6 +37,14 @@ import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
|
||||
class AttachmentDialog(val activity: Activity, var chatController: ChatController) : BottomSheetDialog(activity) {
|
||||
|
||||
@BindView(R.id.menu_share_location)
|
||||
@JvmField
|
||||
var shareLocationItem: LinearLayout? = null
|
||||
|
||||
@BindView(R.id.txt_share_location)
|
||||
@JvmField
|
||||
var shareLocation: AppCompatTextView? = null
|
||||
|
||||
@BindView(R.id.txt_attach_file_from_local)
|
||||
@JvmField
|
||||
var attachFromLocal: AppCompatTextView? = null
|
||||
@ -60,6 +70,19 @@ class AttachmentDialog(val activity: Activity, var chatController: ChatControlle
|
||||
String.format(it.getString(R.string.nc_upload_from_cloud), serverName)
|
||||
}
|
||||
|
||||
if (!CapabilitiesUtil.hasSpreedFeatureCapability(
|
||||
chatController.conversationUser,
|
||||
"geo-location-sharing"
|
||||
)
|
||||
) {
|
||||
shareLocationItem?.visibility = View.GONE
|
||||
}
|
||||
|
||||
shareLocation?.setOnClickListener {
|
||||
chatController.showShareLocationScreen()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
attachFromLocal?.setOnClickListener {
|
||||
chatController.sendSelectLocalFileIntent()
|
||||
dismiss()
|
||||
|
@ -389,4 +389,8 @@ public class ApiUtils {
|
||||
public static String getUrlForUserFields(String baseUrl) {
|
||||
return baseUrl + ocsApiVersion + "/cloud/user/fields";
|
||||
}
|
||||
|
||||
public static String getUrlToSendLocation(String baseUrl, String roomToken) {
|
||||
return baseUrl + ocsApiVersion + "/apps/spreed/api/v1/chat/" + roomToken + "/share";
|
||||
}
|
||||
}
|
||||
|
@ -55,19 +55,6 @@ import android.view.Window;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.appcompat.widget.AppCompatDrawableManager;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.emoji.text.EmojiCompat;
|
||||
|
||||
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
||||
import com.facebook.common.references.CloseableReference;
|
||||
import com.facebook.datasource.DataSource;
|
||||
@ -102,6 +89,19 @@ import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.XmlRes;
|
||||
import androidx.appcompat.widget.AppCompatDrawableManager;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.emoji.text.EmojiCompat;
|
||||
|
||||
public class DisplayUtils {
|
||||
|
||||
private static final String TAG = "DisplayUtils";
|
||||
@ -160,7 +160,7 @@ public class DisplayUtils {
|
||||
return new BitmapDrawable(getRoundedBitmapFromVectorDrawableResource(resources, resource));
|
||||
}
|
||||
|
||||
private static Bitmap getBitmap(Drawable drawable) {
|
||||
public static Bitmap getBitmap(Drawable drawable) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
|
||||
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
@ -66,4 +66,5 @@ object BundleKeys {
|
||||
val KEY_FILE_ID = "KEY_FILE_ID"
|
||||
val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
|
||||
val KEY_SHARED_TEXT = "KEY_SHARED_TEXT"
|
||||
val KEY_GEOCODING_QUERY = "KEY_GEOCODING_QUERY"
|
||||
}
|
||||
|
@ -0,0 +1,312 @@
|
||||
package fr.dudie.nominatim.client;
|
||||
|
||||
/*
|
||||
* [license]
|
||||
* Nominatim Java API client
|
||||
* ~~~~
|
||||
* Copyright (C) 2010 - 2014 Dudie
|
||||
* ~~~~
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser 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 Lesser Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Lesser Public
|
||||
* License along with this program. If not, see
|
||||
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
* [/license]
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.filosganga.geogson.gson.GeometryAdapterFactory;
|
||||
import com.github.filosganga.geogson.jts.JtsAdapterFactory;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import fr.dudie.nominatim.client.request.NominatimLookupRequest;
|
||||
import fr.dudie.nominatim.client.request.NominatimReverseRequest;
|
||||
import fr.dudie.nominatim.client.request.NominatimSearchRequest;
|
||||
import fr.dudie.nominatim.client.request.paramhelper.OsmType;
|
||||
import fr.dudie.nominatim.gson.ArrayOfAddressElementsDeserializer;
|
||||
import fr.dudie.nominatim.gson.ArrayOfPolygonPointsDeserializer;
|
||||
import fr.dudie.nominatim.gson.BoundingBoxDeserializer;
|
||||
import fr.dudie.nominatim.gson.PolygonPointDeserializer;
|
||||
import fr.dudie.nominatim.model.Address;
|
||||
import fr.dudie.nominatim.model.BoundingBox;
|
||||
import fr.dudie.nominatim.model.Element;
|
||||
import fr.dudie.nominatim.model.PolygonPoint;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* An implementation of the Nominatim Api Service.
|
||||
*
|
||||
* @author Jérémie Huchet
|
||||
* @author Sunil D S
|
||||
* @author Andy Scherzinger
|
||||
*/
|
||||
public final class TalkJsonNominatimClient implements NominatimClient {
|
||||
private static final String TAG = "TalkNominationClient";
|
||||
|
||||
/**
|
||||
* UTF-8 encoding.
|
||||
*/
|
||||
public static final String ENCODING_UTF_8 = "UTF-8";
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
|
||||
/**
|
||||
* Gson instance for Nominatim API calls.
|
||||
*/
|
||||
private final Gson gson;
|
||||
|
||||
/**
|
||||
* The url to make search queries.
|
||||
*/
|
||||
private final String searchUrl;
|
||||
|
||||
/**
|
||||
* The url for reverse geocoding.
|
||||
*/
|
||||
private final String reverseUrl;
|
||||
|
||||
/**
|
||||
* The url for address lookup.
|
||||
*/
|
||||
private final String lookupUrl;
|
||||
|
||||
/**
|
||||
* The default search options.
|
||||
*/
|
||||
private final NominatimOptions defaults;
|
||||
|
||||
/**
|
||||
* Creates the json nominatim client.
|
||||
*
|
||||
* @param baseUrl the nominatim server url
|
||||
* @param httpClient an HTTP client
|
||||
* @param email an email to add in the HTTP requests parameters to "sign" them (see
|
||||
* https://wiki.openstreetmap.org/wiki/Nominatim_usage_policy)
|
||||
*/
|
||||
public TalkJsonNominatimClient(final String baseUrl, final OkHttpClient httpClient, final String email) {
|
||||
this(baseUrl, httpClient, email, new NominatimOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the json nominatim client.
|
||||
*
|
||||
* @param baseUrl the nominatim server url
|
||||
* @param httpClient an HTTP client
|
||||
* @param email an email to add in the HTTP requests parameters to "sign" them (see
|
||||
* https://wiki.openstreetmap.org/wiki/Nominatim_usage_policy)
|
||||
* @param defaults defaults options, they override null valued requests options
|
||||
*/
|
||||
public TalkJsonNominatimClient(final String baseUrl, final OkHttpClient httpClient, final String email, final NominatimOptions defaults) {
|
||||
String emailEncoded;
|
||||
try {
|
||||
emailEncoded = URLEncoder.encode(email, ENCODING_UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
emailEncoded = email;
|
||||
}
|
||||
this.searchUrl = String.format("%s/search?format=jsonv2&email=%s", baseUrl.replaceAll("/$", ""), emailEncoded);
|
||||
this.reverseUrl = String.format("%s/reverse?format=jsonv2&email=%s", baseUrl.replaceAll("/$", ""), emailEncoded);
|
||||
this.lookupUrl = String.format("%s/lookup?format=json&email=%s", baseUrl.replaceAll("/$", ""), emailEncoded);
|
||||
|
||||
Log.d(TAG, "API search URL: " + searchUrl);
|
||||
Log.d(TAG, "API reverse URL: " + reverseUrl);
|
||||
|
||||
this.defaults = defaults;
|
||||
|
||||
// prepare gson instance
|
||||
final GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
|
||||
gsonBuilder.registerTypeAdapter(Element[].class, new ArrayOfAddressElementsDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(PolygonPoint.class, new PolygonPointDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(PolygonPoint[].class, new ArrayOfPolygonPointsDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer());
|
||||
|
||||
gsonBuilder.registerTypeAdapterFactory(new JtsAdapterFactory());
|
||||
gsonBuilder.registerTypeAdapterFactory(new GeometryAdapterFactory());
|
||||
|
||||
gson = gsonBuilder.create();
|
||||
|
||||
// prepare httpclient
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#search(fr.dudie.nominatim.client.request.NominatimSearchRequest)
|
||||
*/
|
||||
@Override
|
||||
public List<Address> search(final NominatimSearchRequest search) throws IOException {
|
||||
|
||||
defaults.mergeTo(search);
|
||||
final String apiCall = String.format("%s&%s", searchUrl, search.getQueryString());
|
||||
Log.d(TAG, "search url: " + apiCall);
|
||||
|
||||
Request requesthttp = new Request.Builder()
|
||||
.addHeader("accept", "application/json")
|
||||
.url(apiCall)
|
||||
.build();
|
||||
|
||||
Response response = httpClient.newCall(requesthttp).execute();
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
return gson.fromJson(responseBody.string(), new TypeToken<List<Address>>() {
|
||||
}.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#getAddress(fr.dudie.nominatim.client.request.NominatimReverseRequest)
|
||||
*/
|
||||
@Override
|
||||
public Address getAddress(final NominatimReverseRequest reverse) throws IOException {
|
||||
|
||||
final String apiCall = String.format("%s&%s", reverseUrl, reverse.getQueryString());
|
||||
Log.d(TAG, "reverse geocoding url: " + apiCall);
|
||||
|
||||
Request requesthttp = new Request.Builder()
|
||||
.addHeader("accept", "application/json")
|
||||
.url(apiCall)
|
||||
.build();
|
||||
|
||||
Response response = httpClient.newCall(requesthttp).execute();
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
return gson.fromJson(responseBody.string(), Address.class);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#lookupAddress(fr.dudie.nominatim.client.request.NominatimLookupRequest)
|
||||
*/
|
||||
@Override
|
||||
public List<Address> lookupAddress(final NominatimLookupRequest lookup) throws IOException {
|
||||
|
||||
final String apiCall = String.format("%s&%s", lookupUrl, lookup.getQueryString());
|
||||
Log.d(TAG, "lookup url: " + apiCall);
|
||||
Request requesthttp = new Request.Builder()
|
||||
.addHeader("accept", "application/json")
|
||||
.url(apiCall)
|
||||
.build();
|
||||
|
||||
Response response = httpClient.newCall(requesthttp).execute();
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
return gson.fromJson(responseBody.string(), new TypeToken<List<Address>>() {
|
||||
}.getType());
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#search(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public List<Address> search(final String query) throws IOException {
|
||||
|
||||
final NominatimSearchRequest q = new NominatimSearchRequest();
|
||||
q.setQuery(query);
|
||||
return this.search(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#getAddress(double, double)
|
||||
*/
|
||||
@Override
|
||||
public Address getAddress(final double longitude, final double latitude) throws IOException {
|
||||
|
||||
final NominatimReverseRequest q = new NominatimReverseRequest();
|
||||
q.setQuery(longitude, latitude);
|
||||
return this.getAddress(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#getAddress(double, double, int)
|
||||
*/
|
||||
@Override
|
||||
public Address getAddress(final double longitude, final double latitude, final int zoom)
|
||||
throws IOException {
|
||||
|
||||
final NominatimReverseRequest q = new NominatimReverseRequest();
|
||||
q.setQuery(longitude, latitude);
|
||||
q.setZoom(zoom);
|
||||
return this.getAddress(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#getAddress(int, int)
|
||||
*/
|
||||
@Override
|
||||
public Address getAddress(final int longitudeE6, final int latitudeE6) throws IOException {
|
||||
|
||||
return this.getAddress((longitudeE6 / 1E6), (latitudeE6 / 1E6));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#getAddress(String, long)
|
||||
*/
|
||||
@Override
|
||||
public Address getAddress(final String type, final long id) throws IOException {
|
||||
|
||||
final NominatimReverseRequest q = new NominatimReverseRequest();
|
||||
q.setQuery(OsmType.from(type), id);
|
||||
return this.getAddress(q);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @see fr.dudie.nominatim.client.NominatimClient#lookupAddress(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<Address> lookupAddress(final List<String> typeId) throws IOException {
|
||||
|
||||
final NominatimLookupRequest q = new NominatimLookupRequest();
|
||||
q.setQuery(typeId);
|
||||
return this.lookupAddress(q);
|
||||
}
|
||||
}
|
34
app/src/main/res/drawable-night/ic_circular_location.xml
Normal file
34
app/src/main/res/drawable-night/ic_circular_location.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#2C2C2C"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M12,0C5.4168,0 0,5.4168 0,12C0,18.5832 5.4168,24 12,24C18.5832,24 24,18.5832 24,12C24,5.4168 18.5832,0 12,0Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M12,11.6989C11.1743,11.6989 10.4943,11.0189 10.4943,10.1932C10.4943,9.3675 11.1743,8.6875 12,8.6875C12.8257,8.6875 13.5057,9.3675 13.5057,10.1932C13.5057,11.0189 12.8257,11.6989 12,11.6989M12,5.9774C9.6873,5.9774 7.7842,7.8805 7.7842,10.1932C7.7842,13.3551 12,18.0226 12,18.0226C12,18.0226 16.2158,13.3551 16.2158,10.1932C16.2158,7.8805 14.3127,5.9774 12,5.9774Z" />
|
||||
</vector>
|
12
app/src/main/res/drawable/current_location_circle.xml
Normal file
12
app/src/main/res/drawable/current_location_circle.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#0082C9"/>
|
||||
<stroke android:color="#FFFFFF" android:width="2dp"/>
|
||||
<size android:width="15dp" android:height="15dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
9
app/src/main/res/drawable/ic_baseline_gps_fixed_24.xml
Normal file
9
app/src/main/res/drawable/ic_baseline_gps_fixed_24.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/fontAppbar"
|
||||
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable/ic_baseline_location_on_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_location_on_24.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF0000"
|
||||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
||||
</vector>
|
34
app/src/main/res/drawable/ic_circular_location.xml
Normal file
34
app/src/main/res/drawable/ic_circular_location.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#DBDBDB"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M12,0C5.4168,0 0,5.4168 0,12C0,18.5832 5.4168,24 12,24C18.5832,24 24,18.5832 24,12C24,5.4168 18.5832,0 12,0Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M12,11.6989C11.1743,11.6989 10.4943,11.0189 10.4943,10.1932C10.4943,9.3675 11.1743,8.6875 12,8.6875C12.8257,8.6875 13.5057,9.3675 13.5057,10.1932C13.5057,11.0189 12.8257,11.6989 12,11.6989M12,5.9774C9.6873,5.9774 7.7842,7.8805 7.7842,10.1932C7.7842,13.3551 12,18.0226 12,18.0226C12,18.0226 16.2158,13.3551 16.2158,10.1932C16.2158,7.8805 14.3127,5.9774 12,5.9774Z" />
|
||||
</vector>
|
34
app/src/main/res/layout/controller_geocoding.xml
Normal file
34
app/src/main/res/layout/controller_geocoding.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/parent_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/geocoding_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
</ListView>
|
||||
|
||||
</LinearLayout>
|
124
app/src/main/res/layout/controller_location.xml
Normal file
124
app/src/main/res/layout/controller_location.xml
Normal file
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<LinearLayout 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:id="@+id/parent_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<org.osmdroid.views.MapView
|
||||
android:id="@+id/map"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/centerMapButton"
|
||||
style="@style/Widget.AppTheme.Button.IconButton"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:backgroundTint="@color/bg_default_semitransparent"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:elevation="0dp"
|
||||
app:icon="@drawable/ic_baseline_gps_fixed_24"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp"
|
||||
app:iconSize="24dp"
|
||||
app:iconTint="@color/high_emphasis_text" />
|
||||
|
||||
<View
|
||||
android:id="@+id/locationpicker_anchor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_above="@id/locationpicker_anchor"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="-10dp"
|
||||
android:contentDescription="@string/nc_location_current_position_description"
|
||||
android:src="@drawable/ic_baseline_location_on_red_24" />
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/share_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roundedImageView"
|
||||
android:layout_width="@dimen/avatar_size"
|
||||
android:layout_height="@dimen/avatar_size"
|
||||
android:layout_gravity="top"
|
||||
android:layout_margin="@dimen/standard_margin"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_circular_location" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/share_location_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="16sp"
|
||||
tools:text="Share this location" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/place_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/medium_emphasis_text"
|
||||
android:textSize="14sp"
|
||||
tools:text="Brandenburg, Germany" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -38,6 +38,38 @@
|
||||
android:textColor="@color/medium_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_share_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/standard_padding"
|
||||
android:paddingTop="@dimen/standard_half_padding"
|
||||
android:paddingRight="@dimen/standard_padding"
|
||||
android:paddingBottom="@dimen/standard_half_padding"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_share_location"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_baseline_location_on_24"
|
||||
app:tint="@color/colorPrimary" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_share_location"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="@dimen/standard_margin"
|
||||
android:text="@string/nc_share_location"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_attach_file_from_local"
|
||||
android:layout_width="match_parent"
|
||||
|
56
app/src/main/res/layout/geocoding_item.xml
Normal file
56
app/src/main/res/layout/geocoding_item.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="56dp"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roundedImageView"
|
||||
android:layout_width="@dimen/avatar_size"
|
||||
android:layout_height="@dimen/avatar_size"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginTop="@dimen/standard_margin"
|
||||
android:layout_marginStart="@dimen/standard_half_margin"
|
||||
android:layout_marginEnd="@dimen/standard_double_padding"
|
||||
android:layout_marginBottom="@dimen/standard_margin"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_circular_location" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/geocoding_result_text_size"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="@dimen/standard_padding"
|
||||
android:paddingStart="@dimen/standard_half_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
tools:text="S Sonnenallee, 50, Saalestraße, Rixdorf, Neukölln, Berlin, 12055, Deutschland" />
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ @author Marcel Hibbe
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@id/messageUserAvatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:roundAsCircle="true" />
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@id/bubble"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right"
|
||||
android:layout_toEndOf="@id/messageUserAvatar"
|
||||
android:orientation="vertical"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<include
|
||||
android:id="@+id/message_quote"
|
||||
layout="@layout/item_message_quote"
|
||||
android:visibility="gone" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="200dp" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/messageAuthor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:textColor="@color/textColorMaxContrast"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textIsSelectable="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
@ -2,6 +2,8 @@
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
@ -47,7 +49,10 @@
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||
<include
|
||||
android:id="@+id/message_quote"
|
||||
layout="@layout/item_message_quote"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/messageAuthor"
|
||||
|
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<RelativeLayout 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="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="2dp">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@id/bubble"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<include
|
||||
android:id="@+id/message_quote"
|
||||
layout="@layout/item_message_quote"
|
||||
android:visibility="gone" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="200dp" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:textColorHighlight="@color/nc_grey"
|
||||
android:textIsSelectable="true"
|
||||
tools:text="Talk to ayou later!" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center"
|
||||
tools:text="10:35" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/checkMark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageTime"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
@ -41,7 +41,10 @@
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<include layout="@layout/item_message_quote" android:visibility="gone"/>
|
||||
<include
|
||||
android:id="@+id/message_quote"
|
||||
layout="@layout/item_message_quote"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
|
33
app/src/main/res/menu/menu_geocoding.xml
Normal file
33
app/src/main/res/menu/menu_geocoding.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ Copyright (C) 2017 Mario Danic
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- Search, should appear as action button -->
|
||||
<item android:id="@+id/geocoding_action_search"
|
||||
android:title="@string/nc_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
app:showAsAction="collapseActionView|always"
|
||||
android:animateLayoutChanges="true"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView" />
|
||||
|
||||
</menu>
|
33
app/src/main/res/menu/menu_locationpicker.xml
Normal file
33
app/src/main/res/menu/menu_locationpicker.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Mario Danic
|
||||
~ Copyright (C) 2017 Mario Danic
|
||||
~
|
||||
~ 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 <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- Search, should appear as action button -->
|
||||
<item android:id="@+id/location_action_search"
|
||||
android:title="@string/nc_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
app:showAsAction="collapseActionView|always"
|
||||
android:animateLayoutChanges="true"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView" />
|
||||
|
||||
</menu>
|
@ -39,6 +39,7 @@
|
||||
<color name="low_emphasis_text">#61ffffff</color>
|
||||
|
||||
<color name="bg_default">#121212</color>
|
||||
<color name="bg_default_semitransparent">#99121212</color>
|
||||
<color name="bg_inverse">@color/grey950</color>
|
||||
|
||||
<color name="fg_default">#FFFFFF</color>
|
||||
|
@ -67,6 +67,7 @@
|
||||
<color name="fg_inverse">#FFFFFF</color>
|
||||
|
||||
<color name="bg_default">#FFFFFF</color>
|
||||
<color name="bg_default_semitransparent">#99FFFFFF</color>
|
||||
<color name="bg_inverse">@color/grey950</color>
|
||||
<color name="bg_dark_mention_chips">#333333</color>
|
||||
|
||||
|
@ -38,6 +38,8 @@
|
||||
<dimen name="message_bubble_corners_radius">6dp</dimen>
|
||||
<dimen name="message_bubble_corners_padding">8dp</dimen>
|
||||
|
||||
<dimen name="geocoding_result_text_size">18sp</dimen>
|
||||
|
||||
<dimen name="maximum_file_preview_size">192dp</dimen>
|
||||
|
||||
<dimen name="large_preview_dimension">80dp</dimen>
|
||||
@ -50,6 +52,7 @@
|
||||
<dimen name="standard_double_margin">32dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_width">72dp</dimen>
|
||||
<dimen name="empty_list_icon_layout_height">72dp</dimen>
|
||||
<dimen name="standard_double_padding">32dp</dimen>
|
||||
<dimen name="standard_padding">16dp</dimen>
|
||||
<dimen name="standard_half_padding">8dp</dimen>
|
||||
<dimen name="standard_half_margin">8dp</dimen>
|
||||
|
@ -54,4 +54,11 @@
|
||||
<string name="google_app_id" translatable="false">1:829118773643:android:54b65087c544d819</string>
|
||||
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s</string>
|
||||
<string name="google_storage_bucket" translatable="false">nextcloud-a7dea.appspot.com</string>
|
||||
|
||||
<!-- Map and Geocoding -->
|
||||
<string name="osm_tile_server_url" translatable="false">https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png</string>
|
||||
<string name="osm_tile_server_attributation" translatable="false">OpenStreetMap contributors</string>
|
||||
<string name="osm_geocoder_url" translatable="false">https://nominatim.openstreetmap.org/</string>
|
||||
<string name="osm_geocoder_contact" translatable="false">android@nextcloud.com</string>
|
||||
|
||||
</resources>
|
||||
|
@ -275,12 +275,14 @@
|
||||
<string name="nc_sent_an_audio" formatted="true">%1$s sent an audio.</string>
|
||||
<string name="nc_sent_a_video" formatted="true">%1$s sent a video.</string>
|
||||
<string name="nc_sent_an_image" formatted="true">%1$s sent an image.</string>
|
||||
<string name="nc_sent_location" formatted="true">%1$s sent a location.</string>
|
||||
<string name="nc_sent_a_link_you">You sent a link.</string>
|
||||
<string name="nc_sent_a_gif_you">You sent a GIF.</string>
|
||||
<string name="nc_sent_an_attachment_you">You sent an attachment.</string>
|
||||
<string name="nc_sent_an_audio_you">You sent an audio.</string>
|
||||
<string name="nc_sent_a_video_you">You sent a video.</string>
|
||||
<string name="nc_sent_an_image_you">You sent an image.</string>
|
||||
<string name="nc_sent_location_you">You sent a location.</string>
|
||||
<string name="nc_formatted_message" translatable="false">%1$s: %2$s</string>
|
||||
<string name="nc_message_quote_cancel_reply">Cancel reply</string>
|
||||
<!-- When translating to German, please use non-formal variant -->
|
||||
@ -371,6 +373,13 @@
|
||||
<string name="nc_upload_confirm_send_single">Send this file to %1$s?</string>
|
||||
<string name="nc_upload_in_progess">Uploading</string>
|
||||
|
||||
<!-- location sharing -->
|
||||
<string name="nc_share_location">Share location</string>
|
||||
<string name="nc_location_permission_required">location permission is required</string>
|
||||
<string name="nc_share_current_location">Share current location</string>
|
||||
<string name="nc_share_this_location">Share this location</string>
|
||||
<string name="nc_location_current_position_description">Your current location</string>
|
||||
|
||||
<!-- Phonebook Integration -->
|
||||
<string name="nc_settings_phone_book_integration_key" translatable="false">phone_book_integration</string>
|
||||
<string name="nc_settings_phone_book_integration_desc">Match contacts based on phone number to integrate Talk shortcut into system contacts app</string>
|
||||
|
10
drawable_resources/other/circular_location.svg
Normal file
10
drawable_resources/other/circular_location.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.2,0,0,1.2,-2.4,-2.4)">
|
||||
<path d="M12,2C6.514,2 2,6.514 2,12C2,17.486 6.514,22 12,22C17.486,22 22,17.486 22,12C22,6.514 17.486,2 12,2Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.602263,0,0,0.602263,4.77284,4.77284)">
|
||||
<path d="M12,11.5C10.629,11.5 9.5,10.371 9.5,9C9.5,7.629 10.629,6.5 12,6.5C13.371,6.5 14.5,7.629 14.5,9C14.5,10.371 13.371,11.5 12,11.5M12,2C8.16,2 5,5.16 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9C19,5.16 15.84,2 12,2Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1002 B |
@ -1 +1 @@
|
||||
448
|
||||
436
|
@ -1,2 +1,2 @@
|
||||
DO NOT TOUCH; GENERATED BY DRONE
|
||||
<span class="mdl-layout-title">Lint Report: 3 errors and 290 warnings</span>
|
||||
<span class="mdl-layout-title">Lint Report: 3 errors and 275 warnings</span>
|
||||
|
Loading…
Reference in New Issue
Block a user