Merge pull request #1138 from nextcloud/openAttachmentsDirectly

Open attachments directly
This commit is contained in:
Marcel Hibbe 2021-04-23 16:05:50 +02:00 committed by GitHub
commit 1ec7c7fd3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1156 additions and 229 deletions

View file

@ -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
@ -23,6 +24,7 @@ Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
- remove all "chat via"-links from phonebook when sync is disabled
- fix to show avatars for incoming pictures in group chats (@starypatyk)
- do not allow selecting files in files browser that are not allowed to be reshared
- fix to show all file previews
## [11.1.0] - 2021-03-12
### Added

View file

@ -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'

View file

@ -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" />

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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,42 @@ 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 org.jetbrains.annotations.NotNull;
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 +97,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 +127,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 +195,227 @@ 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();
}
@SuppressLint("LongLogTag")
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(TAG, "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;
default:
// do nothing
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
@ -165,12 +430,16 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
}
@Override
public void onSuccess(ReadFilesystemOperation readFilesystemOperation) {
public void onSuccess(@NotNull ReadFilesystemOperation readFilesystemOperation) {
DavResponse davResponse = readFilesystemOperation.readRemotePath();
if (davResponse.data != null) {
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
if (!browserFileList.isEmpty()) {
new Handler(context.getMainLooper()).post(() -> image.getHierarchy().setPlaceholderImage(context.getDrawable(DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType))));
new Handler(context.getMainLooper()).post(() -> {
int resourceId = DrawableUtils.INSTANCE.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType);
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
image.getHierarchy().setPlaceholderImage(drawable);
});
}
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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"
}
}

View file

@ -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")

View file

@ -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;
}

View file

@ -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) {

View file

@ -1,27 +0,0 @@
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ 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/>.
-->
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="20.57dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:fillType="nonZero"
android:pathData="M0.8571,0C0.3771,0 0,0.3771 0,0.8571L0,23.1429C0,23.6229 0.3771,24 0.8571,24L19.7143,24C20.1943,24 20.5714,23.6229 20.5714,23.1429L20.5714,5.1429L15.4286,0L0.8571,0ZM3.4286,3.4286L13.7143,3.4286L13.7143,5.1429L3.4286,5.1429L3.4286,3.4286ZM3.4286,8.5714L12,8.5714L12,10.2857L3.4286,10.2857L3.4286,8.5714ZM3.4286,13.7143L17.1429,13.7143L17.1429,15.4286L3.4286,15.4286L3.4286,13.7143ZM3.4286,18.8571L10.2857,18.8571L10.2857,20.5714L3.4286,20.5714L3.4286,18.8571Z"
android:strokeColor="#00000000" android:strokeWidth="1"/>
</vector>

View file

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#969696"
android:pathData="M6.94,0.5 C6.7,0.5,6.5,0.7,6.5,0.94 L6.5,2.2 C6,2.34,5.45,2.55,4.97,2.85
L4.06,1.94 C3.9,1.76,3.6,1.75,3.44,1.94 L1.94,3.44 C1.76,3.61,1.76,3.9,1.94,4.06
L2.85,4.97 C2.566,5.45,2.35,5.97,2.2,6.5 L0.94,6.5 C0.7,6.5,0.5,6.7,0.5,6.94
L0.5,9.06 C0.5,9.31,0.69,9.5,0.94,9.5 L2.2,9.5 C2.34,10.04,2.56,10.55,2.85,11.03
L1.94,11.94 C1.76,12.11,1.76,12.39,1.94,12.56 L3.44,14.06
C3.62,14.24,3.9,14.24,4.06,14.06 L4.97,13.15 C5.45,13.435,5.97,13.65,6.5,13.8
L6.5,15.06 C6.5,15.31,6.7,15.5,6.94,15.5 L9.06,15.5
C9.3,15.5,9.51,15.3,9.5,15.06 L9.5,13.8 C10.04,13.66,10.55,13.44,11.03,13.15
L11.94,14.06 C12.11,14.24,12.39,14.24,12.56,14.06 L14.06,12.56
C14.24,12.39,14.24,12.11,14.06,11.94 L13.15,11.03
C13.44,10.55,13.65,10.03,13.8,9.5 L15.06,9.5 C15.3,9.5,15.51,9.3,15.5,9.06
L15.5,6.94 C15.5,6.7,15.3,6.5,15.06,6.5 L13.8,6.5
C13.66,5.96,13.44,5.45,13.15,4.97 L14.06,4.06 C14.24,3.89,14.24,3.61,14.06,3.44
L12.56,1.94 C12.39,1.76,12.11,1.76,11.94,1.94 L11.03,2.85
C10.55,2.56,10.03,2.35,9.5,2.2 L9.5,0.94 C9.5,0.7,9.3,0.5,9.06,0.5 L6.94,0.5 Z
M8,4.5 A3.5,3.5,0,0,1,11.5,8 A3.5,3.5,0,0,1,8,11.5 A3.5,3.5,0,0,1,4.5,8
A3.5,3.5,0,0,1,8,4.5 Z" />
</vector>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#0082c9"
android:fillType="evenOdd"
android:pathData="M1.4609,2 C1.2109,2,1,2.2109,1,2.4609 L1,13.5389
C1,13.7969,1.2029,13.9999,1.4609,13.9999 L14.5389,13.9999
C14.7969,13.9999,14.9999,13.7969,14.9999,13.5389 L14.9999,4.4628
C14.9999,4.2128,14.7889,3.9979,14.5389,3.9979 L7.9999,3.9979 L5.9999,1.9999
L1.4608,1.9999 Z M8,5.8008 C8.8836,5.8008,9.5996,6.5167,9.5996,7.4004
L9.5996,8.1992 L10,8.1992 L10,11 L6,11 L6,8.1992 L6.4004,8.1992 L6.4004,7.4004
C6.4004,6.5167,7.1164,5.8008,8,5.8008 Z M8,6.5996
C7.5581,6.5996,7.1992,6.9585,7.1992,7.4004 L7.1992,8.1992 L8.8008,8.1992
L8.8008,7.4004 C8.8008,6.9585,8.4419,6.5996,8,6.5996 Z" />
</vector>

View file

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#0082c9"
android:fillType="evenOdd"
android:pathData="M1.46,2 C1.21,2,1,2.21,1,2.46 L1,13.54 C1,13.798,1.202,14,1.46,14 L14.54,14
C14.798,14,15,13.798,15,13.54 L15,4.462 C15,4.212,14.79,3.999,14.54,3.999
L8,3.999 L6,2 L1.46,2 Z M7.977,5.793 L11.547,5.793 L11.547,9.178 L10.355,8.05
L8.57,9.743 L7.38,8.613 L9.166,6.923 L7.976,5.793 Z M5.597,6.357 L7.38,6.357
L7.977,6.922 L5.597,6.922 L5.597,11.436 L10.355,11.436 L10.355,9.178
L10.951,9.742 L10.951,11.436 C10.951,11.748,10.686,12,10.356,12 L5.596,12
C5.266,12,5.001,11.748,5.001,11.436 L5.001,6.922
C5.001,6.609,5.267,6.357,5.597,6.357 Z" />
</vector>

View 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>

View 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>

View file

@ -0,0 +1,44 @@
<?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: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>

View file

@ -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,30 @@
app:flexWrap="wrap"
app:justifyContent="flex_end">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image"
<FrameLayout
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"/>
android:adjustViewBounds="true">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitStart"
app:roundedCornerRadius="6dp"
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"

View file

@ -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,30 @@
app:flexWrap="wrap"
app:justifyContent="flex_end">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image"
<FrameLayout
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"/>
android:adjustViewBounds="true">
<!-- SimpleDraweeView does not support wrap_content for layout_width or layout_height attributes! -->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitEnd"
app:roundedCornerRadius="6dp"
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 +78,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 +88,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>

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~ @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
@ -19,15 +18,8 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#0082c9"
android:pathData="M1.46,2 C1.21,2,1,2.21,1,2.46 L1,13.54 C1,13.798,1.202,14,1.46,14 L14.54,14
C14.798,14,15,13.798,15,13.54 L15,4.462 C15,4.212,14.79,3.999,14.54,3.999
L8,3.999 L6,2 L1.46,2 Z M2,5 L14,5 L14,13 L2,13 L2,5 Z" />
</vector>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/openInFiles"
android:title="@string/open_in_files_app" />
</menu>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -19,5 +19,10 @@
-->
<paths>
<files-path name="files" path="/" />
<files-path
name="files"
path="/" />
<cache-path
name="cachedFiles"
path="/" />
</paths>