add ability to upload one or multiple files from local storage #254

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2021-01-08 09:55:21 +01:00
parent c56da7b1f3
commit 0b35971ee5
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
10 changed files with 430 additions and 37 deletions

View File

@ -60,8 +60,6 @@ android {
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
disable 'MissingTranslation' disable 'MissingTranslation'
disable "ValidController"
disable "ValidControllerChangeHandler"
} }
javaCompileOptions { javaCompileOptions {

View File

@ -20,6 +20,8 @@
*/ */
package com.nextcloud.talk.api; package com.nextcloud.talk.api;
import androidx.annotation.Nullable;
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
import com.nextcloud.talk.models.json.chat.ChatOverall; import com.nextcloud.talk.models.json.chat.ChatOverall;
import com.nextcloud.talk.models.json.conversations.RoomOverall; import com.nextcloud.talk.models.json.conversations.RoomOverall;
@ -39,7 +41,6 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import androidx.annotation.Nullable;
import io.reactivex.Observable; import io.reactivex.Observable;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
@ -327,7 +328,6 @@ public interface NcApi {
@PUT @PUT
Observable<GenericOverall> setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state); Observable<GenericOverall> setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state);
@FormUrlEncoded @FormUrlEncoded
@POST @POST
Observable<Void> createRemoteShare(@Nullable @Header("Authorization") String authorization, @Url String url, Observable<Void> createRemoteShare(@Nullable @Header("Authorization") String authorization, @Url String url,
@ -338,15 +338,21 @@ public interface NcApi {
@FormUrlEncoded @FormUrlEncoded
@PUT @PUT
Observable<GenericOverall> setLobbyForConversation(@Header("Authorization") String authorization, Observable<GenericOverall> setLobbyForConversation(@Header("Authorization") String authorization,
@Url String url, @Field("state") Integer state, @Url String url, @Field("state") Integer state,
@Field("timer") Long timer); @Field("timer") Long timer);
@POST @POST
Observable<GenericOverall> setReadStatusPrivacy(@Header("Authorization") String authorization, Observable<GenericOverall> setReadStatusPrivacy(@Header("Authorization") String authorization,
@Url String url, @Url String url,
@Body RequestBody body); @Body RequestBody body);
@POST @POST
Observable<ContactsByNumberOverall> searchContactsByPhoneNumber(@Header("Authorization") String authorization, Observable<ContactsByNumberOverall> searchContactsByPhoneNumber(@Header("Authorization") String authorization,
@Url String url, @Url String url,
@Body RequestBody search); @Body RequestBody search);
@PUT
Observable<Response<GenericOverall>> uploadFile(@Header("Authorization") String authorization,
@Url String url,
@Body RequestBody body);
} }

View File

@ -20,6 +20,7 @@
package com.nextcloud.talk.controllers package com.nextcloud.talk.controllers
import android.app.Activity.RESULT_OK
import android.content.ClipData import android.content.ClipData
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -44,6 +45,9 @@ import androidx.emoji.widget.EmojiEditText
import androidx.emoji.widget.EmojiTextView import androidx.emoji.widget.EmojiTextView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import autodagger.AutoInjector import autodagger.AutoInjector
import butterknife.BindView import butterknife.BindView
import butterknife.OnClick import butterknife.OnClick
@ -70,6 +74,7 @@ import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
import com.nextcloud.talk.controllers.base.BaseController import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.events.UserMentionClickEvent import com.nextcloud.talk.events.UserMentionClickEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverall
@ -80,11 +85,9 @@ import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.mention.Mention import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.presenters.MentionAutocompletePresenter import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.ui.dialog.AttachmentDialog
import com.nextcloud.talk.utils.* import com.nextcloud.talk.utils.*
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
@ -123,46 +126,60 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
@Inject @Inject
@JvmField @JvmField
var ncApi: NcApi? = null var ncApi: NcApi? = null
@Inject @Inject
@JvmField @JvmField
var userUtils: UserUtils? = null var userUtils: UserUtils? = null
@Inject @Inject
@JvmField @JvmField
var appPreferences: AppPreferences? = null var appPreferences: AppPreferences? = null
@Inject @Inject
@JvmField @JvmField
var context: Context? = null var context: Context? = null
@Inject @Inject
@JvmField @JvmField
var eventBus: EventBus? = null var eventBus: EventBus? = null
@BindView(R.id.messagesListView) @BindView(R.id.messagesListView)
@JvmField @JvmField
var messagesListView: MessagesList? = null var messagesListView: MessagesList? = null
@BindView(R.id.messageInputView) @BindView(R.id.messageInputView)
@JvmField @JvmField
var messageInputView: MessageInput? = null var messageInputView: MessageInput? = null
@BindView(R.id.messageInput) @BindView(R.id.messageInput)
@JvmField @JvmField
var messageInput: EmojiEditText? = null var messageInput: EmojiEditText? = null
@BindView(R.id.popupBubbleView) @BindView(R.id.popupBubbleView)
@JvmField @JvmField
var popupBubble: PopupBubble? = null var popupBubble: PopupBubble? = null
@BindView(R.id.progressBar) @BindView(R.id.progressBar)
@JvmField @JvmField
var loadingProgressBar: ProgressBar? = null var loadingProgressBar: ProgressBar? = null
@BindView(R.id.smileyButton) @BindView(R.id.smileyButton)
@JvmField @JvmField
var smileyButton: ImageButton? = null var smileyButton: ImageButton? = null
@BindView(R.id.lobby_view) @BindView(R.id.lobby_view)
@JvmField @JvmField
var lobbyView: RelativeLayout? = null var lobbyView: RelativeLayout? = null
@BindView(R.id.lobby_text_view) @BindView(R.id.lobby_text_view)
@JvmField @JvmField
var conversationLobbyText: TextView? = null var conversationLobbyText: TextView? = null
val disposableList = ArrayList<Disposable>() val disposableList = ArrayList<Disposable>()
@JvmField @JvmField
@BindView(R.id.quotedChatMessageView) @BindView(R.id.quotedChatMessageView)
var quotedChatMessageView: RelativeLayout? = null var quotedChatMessageView: RelativeLayout? = null
@BindView(R.id.callControlToggleChat) @BindView(R.id.callControlToggleChat)
@JvmField @JvmField
var toggleChat: SimpleDraweeView? = null var toggleChat: SimpleDraweeView? = null
@ -378,7 +395,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
messagesListView?.setAdapter(adapter) messagesListView?.setAdapter(adapter)
adapter?.setLoadMoreListener(this) adapter?.setLoadMoreListener(this)
adapter?.setDateHeadersFormatter { format(it) } adapter?.setDateHeadersFormatter { format(it) }
adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message)} adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) }
layoutManager = messagesListView?.layoutManager as LinearLayoutManager? layoutManager = messagesListView?.layoutManager as LinearLayoutManager?
@ -401,7 +418,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
wasDetached = true wasDetached = true
} }
toggleChat?.setOnClickListener{ toggleChat?.setOnClickListener {
(activity as MagicCallActivity).showCall() (activity as MagicCallActivity).showCall()
} }
@ -467,8 +484,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
}) })
messageInputView?.setAttachmentsListener { messageInputView?.setAttachmentsListener {
showBrowserScreen(BrowserController activity?.let { AttachmentDialog(it, this).show() };
.BrowserType.DAV_BROWSER)
} }
messageInputView?.button?.setOnClickListener { v -> submitMessage() } messageInputView?.button?.setOnClickListener { v -> submitMessage() }
@ -495,7 +511,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
private fun checkReadOnlyState() { private fun checkReadOnlyState() {
if (currentConversation != null) { if (currentConversation != null) {
if (currentConversation?.shouldShowLobby(conversationUser)?: false || currentConversation?.conversationReadOnlyState != null && currentConversation?.conversationReadOnlyState == Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY) { if (currentConversation?.shouldShowLobby(conversationUser) ?: false || currentConversation?.conversationReadOnlyState != null && currentConversation?.conversationReadOnlyState == Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY) {
conversationVoiceCallMenuItem?.icon?.alpha = 99 conversationVoiceCallMenuItem?.icon?.alpha = 99
conversationVideoMenuItem?.icon?.alpha = 99 conversationVideoMenuItem?.icon?.alpha = 99
@ -559,7 +575,58 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
} }
} }
private fun showBrowserScreen(browserType: BrowserController.BrowserType) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_CHOOSE_FILE) {
if (resultCode == RESULT_OK) {
uploadFile(data)
}
}
}
private fun uploadFile(intentData: Intent?) {
try {
checkNotNull(intentData)
val files: MutableList<String> = ArrayList()
intentData.clipData?.let {
for (index in 0 until it.itemCount) {
files.add(it.getItemAt(index).uri.toString())
}
} ?: run {
checkNotNull(intentData.data)
intentData.data.let {
files.add(intentData.data.toString())
}
}
require(files.isNotEmpty())
val data: Data = Data.Builder()
.putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, files.toTypedArray())
.putString(UploadAndShareFilesWorker.NC_TARGETPATH, conversationUser?.getAttachmentFolder())
.putString(UploadAndShareFilesWorker.ROOM_TOKEN, roomToken)
.build()
val uploadWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(UploadAndShareFilesWorker::class.java)
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(uploadWorker)
} catch (e: IllegalStateException) {
Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
} catch (e: IllegalArgumentException) {
Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
}
}
fun sendSelectLocalFileIntent() {
val action = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
startActivityForResult(Intent.createChooser(action, context?.resources?.getString(
R.string.nc_upload_choose_local_files)), REQUEST_CODE_CHOOSE_FILE);
}
fun showBrowserScreen(browserType: BrowserController.BrowserType) {
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType)) bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser)) bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
@ -618,13 +685,13 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
emojiPopup = messageInput?.let { emojiPopup = messageInput?.let {
EmojiPopup.Builder.fromRootView(view).setOnEmojiPopupShownListener { EmojiPopup.Builder.fromRootView(view).setOnEmojiPopupShownListener {
if (resources != null) { if (resources != null) {
smileyButton?.setColorFilter(resources!!.getColor(R.color.colorPrimary), PorterDuff.Mode.SRC_IN) smileyButton?.setColorFilter(resources!!.getColor(R.color.colorPrimary), PorterDuff.Mode.SRC_IN)
} }
}.setOnEmojiPopupDismissListener { }.setOnEmojiPopupDismissListener {
smileyButton?.setColorFilter(resources!!.getColor(R.color.emoji_icons), smileyButton?.setColorFilter(resources!!.getColor(R.color.emoji_icons),
PorterDuff.Mode.SRC_IN) PorterDuff.Mode.SRC_IN)
}.setOnEmojiClickListener { emoji, imageView -> messageInput?.editableText?.append(" ") }.build(it) }.setOnEmojiClickListener { emoji, imageView -> messageInput?.editableText?.append(" ") }.build(it)
} }
if (activity != null) { if (activity != null) {
@ -878,11 +945,11 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
if (conversationUser != null) { if (conversationUser != null) {
ncApi!!.sendChatMessage( ncApi!!.sendChatMessage(
credentials, ApiUtils.getUrlForChat( credentials,
conversationUser.baseUrl, ApiUtils.getUrlForChat(conversationUser.baseUrl, roomToken),
roomToken message,
), conversationUser.displayName,
message, conversationUser.displayName, replyTo replyTo
) )
?.subscribeOn(Schedulers.io()) ?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
@ -1457,5 +1524,6 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
private val TAG = "ChatController" private val TAG = "ChatController"
private val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1 private val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
private val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2 private val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
val REQUEST_CODE_CHOOSE_FILE: Int = 555
} }
} }

View File

@ -0,0 +1,178 @@
/*
* 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.jobs
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import androidx.work.*
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_PATHS
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.RequestBody
import retrofit2.Response
import java.io.InputStream
import java.util.*
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerParameters) :
Worker(context, workerParameters) {
@Inject
lateinit var ncApi: NcApi
@Inject
lateinit var userUtils: UserUtils
@Inject
lateinit var appPreferences: AppPreferences
override fun doWork(): Result {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
try {
val currentUser = userUtils.currentUser
val sourcefiles = inputData.getStringArray(DEVICE_SOURCEFILES)
val ncTargetpath = inputData.getString(NC_TARGETPATH)
val roomToken = inputData.getString(ROOM_TOKEN)
checkNotNull(currentUser)
checkNotNull(sourcefiles)
require(sourcefiles.isNotEmpty())
checkNotNull(ncTargetpath)
checkNotNull(roomToken)
for (index in sourcefiles.indices) {
val sourcefileUri = Uri.parse(sourcefiles[index])
var filename = getFileName(sourcefileUri)
val requestBody = createRequestBody(sourcefileUri)
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody)
}
} catch (e: IllegalStateException) {
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
return Result.failure()
} catch (e: IllegalArgumentException) {
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
return Result.failure()
}
return Result.success()
}
private fun createRequestBody(sourcefileUri: Uri): RequestBody? {
var requestBody: RequestBody? = null
try {
val input: InputStream = context.contentResolver.openInputStream(sourcefileUri)!!
val buf = ByteArray(input.available())
while (input.read(buf) != -1);
requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), buf)
} catch (e: Exception) {
Log.e(javaClass.simpleName, "failed to create RequestBody for $sourcefileUri", e)
}
return requestBody
}
private fun uploadFile(currentUser: UserEntity, ncTargetpath: String?, filename: String?, roomToken: String?, requestBody: RequestBody?) {
ncApi.uploadFile(
ApiUtils.getCredentials(currentUser.username, currentUser.token),
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
requestBody
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Response<GenericOverall>> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: Response<GenericOverall>) {
}
override fun onError(e: Throwable) {
Log.e(TAG, "failed to upload file $filename")
}
override fun onComplete() {
shareFile(roomToken, currentUser, ncTargetpath, filename)
}
})
}
private fun shareFile(roomToken: String?, currentUser: UserEntity, ncTargetpath: String?, filename: String?) {
val paths: MutableList<String> = ArrayList()
paths.add("$ncTargetpath/$filename")
val data = Data.Builder()
.putLong(KEY_INTERNAL_USER_ID, currentUser.id)
.putString(KEY_ROOM_TOKEN, roomToken)
.putStringArray(KEY_FILE_PATHS, paths.toTypedArray())
.build()
val shareWorker = OneTimeWorkRequest.Builder(ShareOperationWorker::class.java)
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(shareWorker)
}
private fun getFileName(uri: Uri): String? {
var filename: String? = null
if (uri.scheme == "content") {
val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
try {
if (cursor != null && cursor.moveToFirst()) {
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
} finally {
cursor?.close()
}
}
if (filename == null) {
Log.e(TAG, "failed to get DISPLAY_NAME from uri. using fallback.")
filename = uri.path
val lastIndexOfSlash = filename!!.lastIndexOf('/')
if (lastIndexOfSlash != -1) {
filename = filename.substring(lastIndexOfSlash + 1)
}
}
return filename
}
companion object {
const val TAG = "UploadFileWorker"
const val DEVICE_SOURCEFILES = "DEVICE_SOURCEFILES"
const val NC_TARGETPATH = "NC_TARGETPATH"
const val ROOM_TOKEN = "ROOM_TOKEN"
}
}

View File

@ -186,4 +186,25 @@ public interface User extends Parcelable, Persistable, Serializable {
} }
return false; return false;
} }
default String getAttachmentFolder() {
if (getCapabilities() != null) {
Capabilities capabilities;
try {
capabilities = LoganSquare.parse(getCapabilities(), Capabilities.class);
if (capabilities != null &&
capabilities.getSpreedCapability() != null &&
capabilities.getSpreedCapability().getConfig() != null &&
capabilities.getSpreedCapability().getConfig().containsKey("attachments")) {
HashMap<String, String> map = capabilities.getSpreedCapability().getConfig().get("attachments");
if (map != null && map.containsKey("folder")) {
return map.get("folder");
}
}
} catch (IOException e) {
Log.e("User.java", "Failed to get attachment folder", e);
}
}
return "/Talk";
}
} }

View File

@ -0,0 +1,66 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2021 Marcel Hibbe <marcel.hibbe@nextcloud.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.ui.dialog
import android.app.Activity
import android.os.Bundle
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
import com.nextcloud.talk.controllers.ChatController
class AttachmentDialog(val activity: Activity, var chatController :ChatController) : BottomSheetDialog(activity) {
@BindView(R.id.txt_attach_file_from_local)
@JvmField
var attachFromLocal: AppCompatTextView? = null
@BindView(R.id.txt_attach_file_from_cloud)
@JvmField
var attachFromCloud: AppCompatTextView? = null
private var unbinder: Unbinder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
val view = layoutInflater.inflate(R.layout.dialog_attachment, null);
setContentView(view);
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
unbinder = ButterKnife.bind(this, view);
attachFromLocal?.setOnClickListener {
chatController.sendSelectLocalFileIntent();
dismiss()
}
attachFromCloud?.setOnClickListener {
chatController.showBrowserScreen(BrowserController.BrowserType.DAV_BROWSER)
dismiss()
}
}
}

View File

@ -22,6 +22,8 @@ package com.nextcloud.talk.utils;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.DimenRes;
import com.nextcloud.talk.BuildConfig; import com.nextcloud.talk.BuildConfig;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
@ -32,7 +34,6 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.annotation.DimenRes;
import okhttp3.Credentials; import okhttp3.Credentials;
public class ApiUtils { public class ApiUtils {
@ -245,7 +246,7 @@ public class ApiUtils {
} }
public static String getUrlForAvatarWithNameForGuests(String baseUrl, String name, public static String getUrlForAvatarWithNameForGuests(String baseUrl, String name,
@DimenRes int avatarSize) { @DimenRes int avatarSize) {
avatarSize = Math.round(NextcloudTalkApplication avatarSize = Math.round(NextcloudTalkApplication
.Companion.getSharedApplication().getResources().getDimension(avatarSize)); .Companion.getSharedApplication().getResources().getDimension(avatarSize));
@ -287,4 +288,8 @@ public class ApiUtils {
public static String getUrlForSearchByNumber(String baseUrl) { public static String getUrlForSearchByNumber(String baseUrl) {
return baseUrl + ocsApiVersion + "/cloud/users/search/by-phone"; return baseUrl + ocsApiVersion + "/cloud/users/search/by-phone";
} }
public static String getUrlForFileUpload(String baseUrl, String user, String attachmentFolder, String filename) {
return baseUrl + "/remote.php/dav/files/" + user + attachmentFolder + "/" + filename;
}
} }

View File

@ -54,7 +54,7 @@ object LoggingUtils {
fun sendMailWithAttachment(context: Context) { fun sendMailWithAttachment(context: Context) {
val logFile = context.getFileStreamPath("nc_log.txt") val logFile = context.getFileStreamPath("nc_log.txt")
val emailIntent = Intent(Intent.ACTION_SEND) val emailIntent = Intent(Intent.ACTION_SEND)
val mailto = "android@nextcloud.com" val mailto = "mario@nextcloud.com"
emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(mailto)) emailIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(mailto))
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Talk logs") emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Talk logs")
emailIntent.type = "text/plain" emailIntent.type = "text/plain"

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk application
~
~ @author Marcel Hibbe
~ Copyright (C) 2021 Marcel Hibbe <marcel.hibbe@nextcloud.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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_attach_file_from_local"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="15dp"
android:text="@string/nc_upload_local_file"
android:textSize="20sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_attach_file_from_cloud"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="15dp"
android:text="@string/nc_upload_from_nextcloud"
android:textSize="20sp" />
</LinearLayout>

View File

@ -321,6 +321,12 @@
<string name="nc_copy_message">Copy</string> <string name="nc_copy_message">Copy</string>
<string name="nc_reply">Reply</string> <string name="nc_reply">Reply</string>
<!-- Upload -->
<string name="nc_upload_local_file">Upload local file</string>
<string name="nc_upload_from_nextcloud">Upload from Nextcloud</string>
<string name="nc_upload_failed">Failed to upload file</string>
<string name="nc_upload_choose_local_files">Choose files</string>
<!-- Non-translatable strings --> <!-- Non-translatable strings -->
<string name="path_password_strike_through" translatable="false" <string name="path_password_strike_through" translatable="false"