mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-24 22:15:41 +03:00
open files inside app
Open files directly inside the app. Download file into cache beforehand if not already done. supported file types: jpg, .png, .gif, .mp3, .mp4, .mov, .wav, .txt, .md thanks to @tobiasKaminsky and @starypatyk Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
8a978c726b
commit
3f6f492143
23 changed files with 1155 additions and 58 deletions
|
@ -7,6 +7,7 @@ Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
|
|||
|
||||
## [UNRELEASED]
|
||||
### Added
|
||||
- open files inside app (jpg, .png, .gif, .mp3, .mp4, .mov, .wav, .txt, .md)
|
||||
- edit profile information and privacy settings
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -128,7 +128,8 @@ android {
|
|||
ext {
|
||||
daggerVersion = "2.34.1"
|
||||
powermockVersion = "2.0.9"
|
||||
workVersion = "1.0.1"
|
||||
workVersion = "2.3.0"
|
||||
markwonVersion = "4.6.2"
|
||||
}
|
||||
|
||||
|
||||
|
@ -147,10 +148,10 @@ dependencies {
|
|||
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.1.0'
|
||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||
implementation "android.arch.work:work-runtime:${workVersion}"
|
||||
implementation "android.arch.work:work-rxjava2:${workVersion}"
|
||||
implementation "androidx.work:work-runtime:${workVersion}"
|
||||
implementation "androidx.work:work-rxjava2:${workVersion}"
|
||||
androidTestImplementation "androidx.work:work-testing:${workVersion}"
|
||||
implementation 'com.google.android:flexbox:1.1.1'
|
||||
androidTestImplementation "android.arch.work:work-testing:${workVersion}"
|
||||
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
||||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
||||
})
|
||||
|
@ -242,6 +243,12 @@ dependencies {
|
|||
implementation 'com.afollestad.material-dialogs:lifecycle:3.1.0'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.13.3'
|
||||
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.23'
|
||||
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
|
||||
//implementation 'com.github.dhaval2404:imagepicker:1.8'
|
||||
implementation 'com.github.tobiaskaminsky:ImagePicker:extraFile-SNAPSHOT'
|
||||
|
|
|
@ -108,6 +108,24 @@
|
|||
android:configChanges="orientation|screenSize"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenImageActivity"
|
||||
android:theme="@style/FullScreenImageTheme"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenMediaActivity"
|
||||
android:theme="@style/FullScreenMediaTheme"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenTextViewerActivity"
|
||||
android:theme="@style/FullScreenTextTheme"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize">
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receivers.PackageReplacedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Marcel Hibbe
|
||||
* @author Dariusz Olszewski
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2021 Dariusz Olszewski
|
||||
*
|
||||
* 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.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import com.github.chrisbanes.photoview.PhotoView
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import pl.droidsonroids.gif.GifDrawable
|
||||
import pl.droidsonroids.gif.GifImageView
|
||||
import java.io.File
|
||||
|
||||
|
||||
class FullScreenImageActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var path: String
|
||||
private lateinit var imageWrapperView: FrameLayout
|
||||
private lateinit var photoView: PhotoView
|
||||
private lateinit var gifView: GifImageView
|
||||
|
||||
private var showFullscreen = false
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_preview, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, shareUri)
|
||||
type = "image/*"
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
|
||||
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_full_screen_image)
|
||||
setSupportActionBar(findViewById(R.id.imageview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
|
||||
imageWrapperView = findViewById(R.id.image_wrapper_view)
|
||||
photoView = findViewById(R.id.photo_view)
|
||||
gifView = findViewById(R.id.gif_view)
|
||||
|
||||
photoView.setOnPhotoTapListener{ view, x, y ->
|
||||
toggleFullscreen()
|
||||
}
|
||||
photoView.setOnOutsidePhotoTapListener{
|
||||
toggleFullscreen()
|
||||
}
|
||||
gifView.setOnClickListener{
|
||||
toggleFullscreen()
|
||||
}
|
||||
|
||||
val fileName = intent.getStringExtra("FILE_NAME")
|
||||
val isGif = intent.getBooleanExtra("IS_GIF", false)
|
||||
|
||||
path = applicationContext.cacheDir.absolutePath + "/" + fileName
|
||||
if (isGif) {
|
||||
photoView.visibility = View.INVISIBLE
|
||||
gifView.visibility = View.VISIBLE
|
||||
val gifFromUri = GifDrawable(path)
|
||||
gifView.setImageDrawable(gifFromUri)
|
||||
} else {
|
||||
gifView.visibility = View.INVISIBLE
|
||||
photoView.visibility = View.VISIBLE
|
||||
photoView.setImageURI(Uri.parse(path))
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleFullscreen(){
|
||||
showFullscreen = !showFullscreen;
|
||||
if (showFullscreen){
|
||||
hideSystemUI()
|
||||
supportActionBar?.hide()
|
||||
} else{
|
||||
showSystemUI()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
|
||||
private fun showSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ui.PlayerControlView
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import java.io.File
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class FullScreenMediaActivity : AppCompatActivity(), Player.EventListener {
|
||||
|
||||
private lateinit var path: String
|
||||
private lateinit var playerView: StyledPlayerView
|
||||
private lateinit var player: SimpleExoPlayer
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_preview, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, shareUri)
|
||||
type = "video/*"
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
|
||||
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val fileName = intent.getStringExtra("FILE_NAME")
|
||||
val isAudioOnly = intent.getBooleanExtra("AUDIO_ONLY", false)
|
||||
|
||||
path = applicationContext.cacheDir.absolutePath + "/" + fileName
|
||||
|
||||
setContentView(R.layout.activity_full_screen_media)
|
||||
setSupportActionBar(findViewById(R.id.mediaview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
|
||||
playerView = findViewById(R.id.player_view)
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
playerView.showController()
|
||||
if (isAudioOnly) {
|
||||
playerView.controllerShowTimeoutMs = 0
|
||||
}
|
||||
|
||||
playerView.setControllerVisibilityListener { v ->
|
||||
if (v != 0) {
|
||||
hideSystemUI()
|
||||
supportActionBar?.hide()
|
||||
} else {
|
||||
showSystemUI()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
initializePlayer()
|
||||
|
||||
val mediaItem: MediaItem = MediaItem.fromUri(path)
|
||||
player.setMediaItem(mediaItem)
|
||||
player.prepare()
|
||||
player.play()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
releasePlayer()
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
player = SimpleExoPlayer.Builder(applicationContext).build()
|
||||
playerView.player = player;
|
||||
player.playWhenReady = true
|
||||
player.addListener(this)
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
player.release()
|
||||
}
|
||||
|
||||
private fun hideSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
|
||||
private fun showSystemUI() {
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import io.noties.markwon.Markwon
|
||||
import java.io.File
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class FullScreenTextViewerActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var path: String
|
||||
private lateinit var textView: TextView
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_preview, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path))
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, shareUri)
|
||||
type = "text/*"
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
|
||||
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_full_screen_text)
|
||||
setSupportActionBar(findViewById(R.id.textview_toolbar))
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false);
|
||||
|
||||
textView = findViewById(R.id.text_view)
|
||||
|
||||
val fileName = intent.getStringExtra("FILE_NAME")
|
||||
val isMarkdown = intent.getBooleanExtra("IS_MARKDOWN", false)
|
||||
path = applicationContext.cacheDir.absolutePath + "/" + fileName
|
||||
var text = readFile(path)
|
||||
|
||||
if (isMarkdown) {
|
||||
val markwon = Markwon.create(applicationContext);
|
||||
markwon.setMarkdown(textView, text);
|
||||
} else {
|
||||
textView.text = text
|
||||
}
|
||||
}
|
||||
|
||||
private fun readFile(fileName: String) = File(fileName).inputStream().readBytes().toString(Charsets.UTF_8)
|
||||
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
* 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
|
||||
|
@ -28,19 +30,21 @@ import android.graphics.drawable.Drawable;
|
|||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
|
||||
import autodagger.AutoInjector;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.activities.FullScreenImageActivity;
|
||||
import com.nextcloud.talk.activities.FullScreenMediaActivity;
|
||||
import com.nextcloud.talk.activities.FullScreenTextViewerActivity;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||
import com.nextcloud.talk.utils.AccountUtils;
|
||||
|
@ -48,22 +52,39 @@ import com.nextcloud.talk.utils.DisplayUtils;
|
|||
import com.nextcloud.talk.utils.DrawableUtils;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
import androidx.work.Data;
|
||||
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.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
|
||||
|
||||
private static String TAG = "MagicPreviewMessageViewHolder";
|
||||
|
||||
@BindView(R.id.messageText)
|
||||
EmojiTextView messageText;
|
||||
|
||||
View progressBar;
|
||||
|
||||
@Inject
|
||||
Context context;
|
||||
|
||||
|
@ -73,6 +94,7 @@ 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);
|
||||
}
|
||||
|
||||
|
@ -102,35 +124,54 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
|||
}
|
||||
|
||||
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
// it's a preview for a Nextcloud share
|
||||
messageText.setText(message.getSelectedIndividualHashMap().get("name"));
|
||||
DisplayUtils.setClickableString(message.getSelectedIndividualHashMap().get("name"), message.getSelectedIndividualHashMap().get("link"), messageText);
|
||||
String fileName = message.getSelectedIndividualHashMap().get("name");
|
||||
messageText.setText(fileName);
|
||||
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
||||
image.getHierarchy().setPlaceholderImage(context.getDrawable(DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(message.getSelectedIndividualHashMap().get("mimetype"))));
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
int drawableResourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(mimetype);
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableResourceId);
|
||||
image.getHierarchy().setPlaceholderImage(drawable);
|
||||
} else {
|
||||
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get("path"), message.activeUser);
|
||||
}
|
||||
|
||||
image.setOnClickListener(v -> {
|
||||
|
||||
String accountString =
|
||||
message.activeUser.getUsername() + "@" + message.activeUser.getBaseUrl().replace("https://", "").replace("http://", "");
|
||||
|
||||
if (AccountUtils.INSTANCE.canWeOpenFilesApp(context, accountString)) {
|
||||
Intent filesAppIntent = new Intent(Intent.ACTION_VIEW, null);
|
||||
final ComponentName componentName = new ComponentName(context.getString(R.string.nc_import_accounts_from), "com.owncloud.android.ui.activity.FileDisplayActivity");
|
||||
filesAppIntent.setComponent(componentName);
|
||||
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
filesAppIntent.setPackage(context.getString(R.string.nc_import_accounts_from));
|
||||
filesAppIntent.putExtra(BundleKeys.INSTANCE.getKEY_ACCOUNT(), accountString);
|
||||
filesAppIntent.putExtra(BundleKeys.INSTANCE.getKEY_FILE_ID(), message.getSelectedIndividualHashMap().get("id"));
|
||||
context.startActivity(filesAppIntent);
|
||||
image.setOnClickListener(v -> {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
if (isSupportedMimetype(mimetype)) {
|
||||
openOrDownloadFile(message);
|
||||
} else {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(message.getSelectedIndividualHashMap().get("link")));
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(browserIntent);
|
||||
openFileInFilesApp(message, accountString);
|
||||
}
|
||||
});
|
||||
|
||||
image.setOnLongClickListener(l -> {
|
||||
onMessageViewLongClick(message, accountString);
|
||||
return true;
|
||||
});
|
||||
|
||||
// check if download worker is already running
|
||||
String fileId = message.getSelectedIndividualHashMap().get("id");
|
||||
ListenableFuture<List<WorkInfo>> workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId);
|
||||
|
||||
try {
|
||||
for (WorkInfo workInfo : workers.get()) {
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING || workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workInfo.getId()).observeForever(info -> {
|
||||
updateViewsByProgress(fileName, mimetype, info);
|
||||
});
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
|
@ -151,6 +192,222 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isSupportedMimetype(String mimetype){
|
||||
switch (mimetype) {
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/gif":
|
||||
case "audio/mpeg":
|
||||
case "audio/wav":
|
||||
case "audio/ogg":
|
||||
case "video/mp4":
|
||||
case "video/quicktime":
|
||||
case "video/ogg":
|
||||
case "text/markdown":
|
||||
case "text/plain":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openOrDownloadFile(ChatMessage message) {
|
||||
String filename = message.getSelectedIndividualHashMap().get("name");
|
||||
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||
File file = new File(context.getCacheDir(), filename);
|
||||
if (file.exists()) {
|
||||
openFile(filename, mimetype);
|
||||
} else {
|
||||
String size = message.getSelectedIndividualHashMap().get("size");
|
||||
|
||||
if (size == null) {
|
||||
size = "-1";
|
||||
}
|
||||
Integer fileSize = Integer.valueOf(size);
|
||||
|
||||
String fileId = message.getSelectedIndividualHashMap().get("id");
|
||||
String path = message.getSelectedIndividualHashMap().get("path");
|
||||
downloadFileToCache(
|
||||
message.activeUser.getBaseUrl(),
|
||||
message.activeUser.getUserId(),
|
||||
message.activeUser.getAttachmentFolder(),
|
||||
filename,
|
||||
path,
|
||||
mimetype,
|
||||
fileSize,
|
||||
fileId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile(String filename, String mimetype) {
|
||||
switch (mimetype) {
|
||||
case "audio/mpeg":
|
||||
case "audio/wav":
|
||||
case "audio/ogg":
|
||||
case "video/mp4":
|
||||
case "video/quicktime":
|
||||
case "video/ogg":
|
||||
openMediaView(filename, mimetype);
|
||||
break;
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/gif":
|
||||
openImageView(filename, mimetype);
|
||||
break;
|
||||
case "text/markdown":
|
||||
case "text/plain":
|
||||
openTextView(filename, mimetype);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "no method defined for mimetype: " + mimetype);
|
||||
}
|
||||
}
|
||||
|
||||
private void openImageView(String filename, String mimetype) {
|
||||
Intent fullScreenImageIntent = new Intent(context, FullScreenImageActivity.class);
|
||||
fullScreenImageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenImageIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenImageIntent.putExtra("IS_GIF", isGif(mimetype));
|
||||
context.startActivity(fullScreenImageIntent);
|
||||
}
|
||||
|
||||
private void openFileInFilesApp(ChatMessage message, String accountString) {
|
||||
if (AccountUtils.INSTANCE.canWeOpenFilesApp(context, accountString)) {
|
||||
Intent filesAppIntent = new Intent(Intent.ACTION_VIEW, null);
|
||||
final ComponentName componentName = new ComponentName(context.getString(R.string.nc_import_accounts_from), "com.owncloud.android.ui.activity.FileDisplayActivity");
|
||||
filesAppIntent.setComponent(componentName);
|
||||
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
filesAppIntent.setPackage(context.getString(R.string.nc_import_accounts_from));
|
||||
filesAppIntent.putExtra(BundleKeys.INSTANCE.getKEY_ACCOUNT(), accountString);
|
||||
filesAppIntent.putExtra(BundleKeys.INSTANCE.getKEY_FILE_ID(), message.getSelectedIndividualHashMap().get("id"));
|
||||
context.startActivity(filesAppIntent);
|
||||
} else {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(message.getSelectedIndividualHashMap().get("link")));
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(browserIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageViewLongClick(ChatMessage message, String accountString) {
|
||||
if (isSupportedMimetype(message.getSelectedIndividualHashMap().get("mimetype"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
PopupMenu popupMenu = new PopupMenu(this.context, itemView, Gravity.START);
|
||||
popupMenu.inflate(R.menu.chat_preview_message_menu);
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(item -> {
|
||||
openFileInFilesApp(message, accountString);
|
||||
return true;
|
||||
});
|
||||
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
private void downloadFileToCache(String baseUrl,
|
||||
String userId,
|
||||
String attachmentFolder,
|
||||
String fileName,
|
||||
String path,
|
||||
String mimetype,
|
||||
Integer size,
|
||||
String fileId) {
|
||||
|
||||
// check if download worker is already running
|
||||
ListenableFuture<List<WorkInfo>> workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId);
|
||||
|
||||
try {
|
||||
for (WorkInfo workInfo : workers.get()) {
|
||||
if (workInfo.getState() == WorkInfo.State.RUNNING || workInfo.getState() == WorkInfo.State.ENQUEUED) {
|
||||
Log.d("Download", "Download worker for " + fileId + " is already running or scheduled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Error when checking if worker already exsists", e);
|
||||
}
|
||||
|
||||
Data data;
|
||||
OneTimeWorkRequest downloadWorker;
|
||||
|
||||
data = new Data.Builder()
|
||||
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
|
||||
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
|
||||
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
|
||||
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, size)
|
||||
.build();
|
||||
|
||||
downloadWorker = new OneTimeWorkRequest.Builder(DownloadFileToCacheWorker.class)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker);
|
||||
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.getId()).observeForever(workInfo -> {
|
||||
updateViewsByProgress(fileName, mimetype, workInfo);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateViewsByProgress(String fileName, String mimetype, WorkInfo workInfo) {
|
||||
switch (workInfo.getState()) {
|
||||
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));
|
||||
}
|
||||
break;
|
||||
|
||||
case SUCCEEDED:
|
||||
if (image.isShown()) {
|
||||
openFile(fileName, mimetype);
|
||||
} else {
|
||||
Log.d(TAG, "image " + fileName + " was downloaded but it's not opened (view is not shown)");
|
||||
}
|
||||
messageText.setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
messageText.setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void openMediaView(String filename, String mimetype) {
|
||||
Intent fullScreenMediaIntent = new Intent(context, FullScreenMediaActivity.class);
|
||||
fullScreenMediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenMediaIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenMediaIntent.putExtra("AUDIO_ONLY", isAudioOnly(mimetype));
|
||||
context.startActivity(fullScreenMediaIntent);
|
||||
}
|
||||
|
||||
private void openTextView(String filename, String mimetype) {
|
||||
Intent fullScreenTextViewerIntent = new Intent(context, FullScreenTextViewerActivity.class);
|
||||
fullScreenTextViewerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenTextViewerIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenTextViewerIntent.putExtra("IS_MARKDOWN", isMarkdown(mimetype));
|
||||
context.startActivity(fullScreenTextViewerIntent);
|
||||
}
|
||||
|
||||
private boolean isGif(String mimetype) {
|
||||
return ("image/gif").equals(mimetype);
|
||||
}
|
||||
|
||||
private boolean isMarkdown(String mimetype) {
|
||||
return ("text/markdown").equals(mimetype);
|
||||
}
|
||||
|
||||
private boolean isAudioOnly(String mimetype) {
|
||||
return mimetype.startsWith("audio");
|
||||
}
|
||||
|
||||
private void fetchFileInformation(String url, UserEntity activeUser) {
|
||||
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||
@Override
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2017 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
|
||||
|
@ -374,6 +376,10 @@ public interface NcApi {
|
|||
@Url String url,
|
||||
@Body RequestBody body);
|
||||
|
||||
@GET
|
||||
Call<ResponseBody> downloadFile(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
||||
@DELETE
|
||||
Observable<ChatOverallSingleMessage> deleteChatMessage(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2017-2019 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
|
||||
|
@ -435,8 +437,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|||
|
||||
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
|
||||
if (newMessagesCount != 0 && layoutManager != null) {
|
||||
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() <
|
||||
newMessagesCount) {
|
||||
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) {
|
||||
newMessagesCount = 0
|
||||
|
||||
if (popupBubble != null && popupBubble!!.isShown) {
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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.util.Log
|
||||
import androidx.work.Data
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
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.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class DownloadFileToCacheWorker(val context: Context, workerParameters: WorkerParameters) :
|
||||
Worker(context, workerParameters) {
|
||||
|
||||
private var totalFileSize: Int = -1
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@Inject
|
||||
lateinit var userUtils: UserUtils
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
override fun doWork(): Result {
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
if (totalFileSize > -1) {
|
||||
setProgressAsync(Data.Builder().putInt(PROGRESS, 0).build())
|
||||
}
|
||||
|
||||
try {
|
||||
val currentUser = userUtils.currentUser
|
||||
val baseUrl = inputData.getString(KEY_BASE_URL)
|
||||
val userId = inputData.getString(KEY_USER_ID)
|
||||
val attachmentFolder = inputData.getString(KEY_ATTACHMENT_FOLDER)
|
||||
val fileName = inputData.getString(KEY_FILE_NAME)
|
||||
val remotePath = inputData.getString(KEY_FILE_PATH)
|
||||
totalFileSize = (inputData.getInt(KEY_FILE_SIZE, -1))
|
||||
|
||||
checkNotNull(currentUser)
|
||||
checkNotNull(baseUrl)
|
||||
checkNotNull(userId)
|
||||
checkNotNull(attachmentFolder)
|
||||
checkNotNull(fileName)
|
||||
checkNotNull(remotePath)
|
||||
|
||||
val url = ApiUtils.getUrlForFileDownload(baseUrl, userId, remotePath)
|
||||
|
||||
return downloadFile(currentUser, url, fileName)
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(javaClass.simpleName, "Something went wrong when trying to download file", e)
|
||||
return Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadFile(currentUser: UserEntity, url: String, fileName: String): Result {
|
||||
val downloadCall = ncApi.downloadFile(
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
url)
|
||||
|
||||
return executeDownload(downloadCall.execute().body(), fileName)
|
||||
}
|
||||
|
||||
private fun executeDownload(body: ResponseBody?, fileName: String): Result {
|
||||
if (body == null) {
|
||||
Log.e(TAG, "Response body when downloading $fileName is null!")
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
var count: Int
|
||||
val data = ByteArray(1024 * 4)
|
||||
val bis: InputStream = BufferedInputStream(body.byteStream(), 1024 * 8)
|
||||
val outputFile = File(context.cacheDir, fileName + "_")
|
||||
val output: OutputStream = FileOutputStream(outputFile)
|
||||
var total: Long = 0
|
||||
val startTime = System.currentTimeMillis()
|
||||
var timeCount = 1
|
||||
|
||||
count = bis.read(data)
|
||||
|
||||
while (count != -1) {
|
||||
if (totalFileSize > -1) {
|
||||
total += count.toLong()
|
||||
val progress = (total * 100 / totalFileSize).toInt()
|
||||
val currentTime = System.currentTimeMillis() - startTime
|
||||
if (currentTime > 50 * timeCount) {
|
||||
setProgressAsync(Data.Builder().putInt(PROGRESS, progress).build())
|
||||
timeCount++
|
||||
}
|
||||
}
|
||||
output.write(data, 0, count)
|
||||
count = bis.read(data)
|
||||
}
|
||||
|
||||
output.flush()
|
||||
output.close()
|
||||
bis.close()
|
||||
|
||||
return onDownloadComplete(fileName)
|
||||
}
|
||||
|
||||
private fun onDownloadComplete(fileName: String): Result {
|
||||
val tempFile = File(context.cacheDir, fileName + "_")
|
||||
val targetFile = File(context.cacheDir, fileName)
|
||||
|
||||
return if (tempFile.renameTo(targetFile)) {
|
||||
setProgressAsync(Data.Builder().putBoolean(SUCCESS, true).build())
|
||||
Result.success()
|
||||
} else {
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DownloadFileToCache"
|
||||
const val KEY_BASE_URL = "KEY_BASE_URL"
|
||||
const val KEY_USER_ID = "KEY_USER_ID"
|
||||
const val KEY_ATTACHMENT_FOLDER = "KEY_ATTACHMENT_FOLDER"
|
||||
const val KEY_FILE_NAME = "KEY_FILE_NAME"
|
||||
const val KEY_FILE_PATH = "KEY_FILE_PATH"
|
||||
const val KEY_FILE_SIZE = "KEY_FILE_SIZE"
|
||||
const val PROGRESS = "PROGRESS"
|
||||
const val SUCCESS = "SUCCESS"
|
||||
|
||||
}
|
||||
}
|
|
@ -21,9 +21,7 @@
|
|||
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
|
||||
|
@ -45,6 +43,8 @@ import io.reactivex.schedulers.Schedulers
|
|||
import okhttp3.MediaType
|
||||
import okhttp3.RequestBody
|
||||
import retrofit2.Response
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
@ -80,9 +80,9 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
|
||||
for (index in sourcefiles.indices) {
|
||||
val sourcefileUri = Uri.parse(sourcefiles[index])
|
||||
var filename = UriUtils.getFileName(sourcefileUri, context)
|
||||
val filename = UriUtils.getFileName(sourcefileUri, context)
|
||||
val requestBody = createRequestBody(sourcefileUri)
|
||||
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody)
|
||||
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody, sourcefileUri)
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
|
||||
|
@ -107,7 +107,8 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
return requestBody
|
||||
}
|
||||
|
||||
private fun uploadFile(currentUser: UserEntity, ncTargetpath: String?, filename: String?, roomToken: String?, requestBody: RequestBody?) {
|
||||
private fun uploadFile(currentUser: UserEntity, ncTargetpath: String?, filename: String, roomToken: String?,
|
||||
requestBody: RequestBody?, sourcefileUri: Uri) {
|
||||
ncApi.uploadFile(
|
||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
|
||||
|
@ -128,10 +129,23 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||
|
||||
override fun onComplete() {
|
||||
shareFile(roomToken, currentUser, ncTargetpath, filename)
|
||||
copyFileToCache(sourcefileUri, filename)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun copyFileToCache(sourceFileUri: Uri, filename: String) {
|
||||
val cachedFile = File(context.cacheDir, filename)
|
||||
val outputStream = FileOutputStream(cachedFile)
|
||||
val inputStream: InputStream = context.contentResolver.openInputStream(sourceFileUri)!!
|
||||
|
||||
inputStream.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFile(roomToken: String?, currentUser: UserEntity, ncTargetpath: String?, filename: String?) {
|
||||
val paths: MutableList<String> = ArrayList()
|
||||
paths.add("$ncTargetpath/$filename")
|
||||
|
|
|
@ -299,6 +299,10 @@ public class ApiUtils {
|
|||
return baseUrl + "/remote.php/dav/files/" + user + attachmentFolder + "/" + filename;
|
||||
}
|
||||
|
||||
public static String getUrlForFileDownload(String baseUrl, String user, String remotePath) {
|
||||
return baseUrl + "/remote.php/dav/files/" + user + "/" + remotePath;
|
||||
}
|
||||
|
||||
public static String getUrlForMessageDeletion(String baseUrl, String token, String messageId) {
|
||||
return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token + "/" + messageId;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ object UriUtils {
|
|||
}
|
||||
}
|
||||
if (filename == null) {
|
||||
Log.e(UploadAndShareFilesWorker.TAG, "failed to get DISPLAY_NAME from uri. using fallback.")
|
||||
Log.e("UriUtils", "failed to get DISPLAY_NAME from uri. using fallback.")
|
||||
filename = uri.path
|
||||
val lastIndexOfSlash = filename!!.lastIndexOf('/')
|
||||
if (lastIndexOfSlash != -1) {
|
||||
|
|
55
app/src/main/res/layout/activity_full_screen_image.xml
Normal file
55
app/src/main/res/layout/activity_full_screen_image.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Marcel Hibbe
|
||||
~ @author Dariusz Olszewski
|
||||
~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ Copyright (C) 2021 Dariusz Olszewski
|
||||
~
|
||||
~ 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/>.
|
||||
-->
|
||||
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/image_wrapper_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".activities.FullScreenImageActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/imageview_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
<com.github.chrisbanes.photoview.PhotoView
|
||||
android:id="@+id/photo_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<pl.droidsonroids.gif.GifImageView
|
||||
android:id="@+id/gif_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
45
app/src/main/res/layout/activity_full_screen_media.xml
Normal file
45
app/src/main/res/layout/activity_full_screen_media.xml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?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/>.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".activities.FullScreenMediaActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/mediaview_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:show_buffering="when_playing"
|
||||
app:show_shuffle_button="true"/>
|
||||
|
||||
</FrameLayout>
|
45
app/src/main/res/layout/activity_full_screen_text.xml
Normal file
45
app/src/main/res/layout/activity_full_screen_text.xml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?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/>.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".activities.FullScreenTextViewerActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/textview_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
tools:text="Lorem Ipsum"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -2,7 +2,9 @@
|
|||
~ 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
|
||||
|
@ -48,16 +50,28 @@
|
|||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
android:adjustViewBounds="true">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||
tools:src="@drawable/ic_call_black_24dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
~ 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
|
||||
|
@ -40,17 +42,28 @@
|
|||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
android:adjustViewBounds="true">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:actualImageScaleType="fitCenter"
|
||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
||||
tools:src="@drawable/ic_call_black_24dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@id/messageText"
|
||||
|
@ -63,7 +76,8 @@
|
|||
android:textSize="12sp"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true" />
|
||||
app:layout_wrapBefore="true"
|
||||
tools:text="Message" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
|
@ -72,7 +86,8 @@
|
|||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/warm_grey_four"
|
||||
app:layout_alignSelf="center" />
|
||||
app:layout_alignSelf="center"
|
||||
tools:text="12:34:56" />
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
25
app/src/main/res/menu/chat_preview_message_menu.xml
Normal file
25
app/src/main/res/menu/chat_preview_message_menu.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tobias Kaminski
|
||||
~ Copyright (C) 2021 Tobias Kaminski <tobias@kaminsky.me>
|
||||
~
|
||||
~ 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">
|
||||
<item
|
||||
android:id="@+id/openInFiles"
|
||||
android:title="@string/open_in_files_app" />
|
||||
</menu>
|
25
app/src/main/res/menu/menu_preview.xml
Normal file
25
app/src/main/res/menu/menu_preview.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tobias Kaminski
|
||||
~ Copyright (C) 2021 Tobias Kaminski <tobias@kaminsky.me>
|
||||
~
|
||||
~ 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">
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share" />
|
||||
</menu>
|
|
@ -341,6 +341,10 @@
|
|||
<string name="nc_delete_message">Delete</string>
|
||||
<string name="nc_delete_message_leaked_to_matterbridge">Message deleted successfully, but it might have been leaked to other services</string>
|
||||
|
||||
<string name="share">Share</string>
|
||||
<string name="send_to">Send to</string>
|
||||
<string name="open_in_files_app">Open in Files app</string>
|
||||
|
||||
<!-- Upload -->
|
||||
<string name="nc_upload_local_file">Upload local file</string>
|
||||
<string name="nc_upload_from_cloud">Share from %1$s</string>
|
||||
|
@ -403,4 +407,5 @@
|
|||
<string name="nc_action_open_main_menu">Open main menu</string>
|
||||
<string name="failed_to_save">Failed to save %1$s</string>
|
||||
<string name="selected_list_item">selected</string>
|
||||
<string name="filename_progress">%1$s (%2$d)</string>
|
||||
</resources>
|
||||
|
|
|
@ -133,6 +133,26 @@
|
|||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="FullScreenImageTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:navigationBarColor">@color/black</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="FullScreenMediaTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:navigationBarColor">@color/black</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="FullScreenTextTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:navigationBarColor">@color/black</item>
|
||||
</style>
|
||||
|
||||
<!-- Launch screen -->
|
||||
<style name="AppTheme.Launcher">
|
||||
<item name="android:windowBackground">@drawable/launch_screen</item>
|
||||
|
|
|
@ -19,5 +19,10 @@
|
|||
-->
|
||||
|
||||
<paths>
|
||||
<files-path name="files" path="/" />
|
||||
<files-path
|
||||
name="files"
|
||||
path="/" />
|
||||
<cache-path
|
||||
name="cachedFiles"
|
||||
path="/" />
|
||||
</paths>
|
||||
|
|
Loading…
Reference in a new issue