mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-10 14:24:05 +01: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
@ -7,6 +7,7 @@ Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
|
|||||||
|
|
||||||
## [UNRELEASED]
|
## [UNRELEASED]
|
||||||
### Added
|
### Added
|
||||||
|
- open files inside app (jpg, .png, .gif, .mp3, .mp4, .mov, .wav, .txt, .md)
|
||||||
- edit profile information and privacy settings
|
- edit profile information and privacy settings
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -128,7 +128,8 @@ android {
|
|||||||
ext {
|
ext {
|
||||||
daggerVersion = "2.34.1"
|
daggerVersion = "2.34.1"
|
||||||
powermockVersion = "2.0.9"
|
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 'com.github.vanniktech:Emoji:0.6.0'
|
||||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.1.0'
|
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.1.0'
|
||||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||||
implementation "android.arch.work:work-runtime:${workVersion}"
|
implementation "androidx.work:work-runtime:${workVersion}"
|
||||||
implementation "android.arch.work:work-rxjava2:${workVersion}"
|
implementation "androidx.work:work-rxjava2:${workVersion}"
|
||||||
|
androidTestImplementation "androidx.work:work-testing:${workVersion}"
|
||||||
implementation 'com.google.android:flexbox:1.1.1'
|
implementation 'com.google.android:flexbox:1.1.1'
|
||||||
androidTestImplementation "android.arch.work:work-testing:${workVersion}"
|
|
||||||
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
||||||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
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.afollestad.material-dialogs:lifecycle:3.1.0'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
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.dhaval2404:imagepicker:1.8'
|
||||||
implementation 'com.github.tobiaskaminsky:ImagePicker:extraFile-SNAPSHOT'
|
implementation 'com.github.tobiaskaminsky:ImagePicker:extraFile-SNAPSHOT'
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||||
@ -108,6 +108,24 @@
|
|||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:launchMode="singleTask" />
|
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">
|
<receiver android:name=".receivers.PackageReplacedReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
<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
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
* 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
|
* 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
|
* 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.graphics.drawable.LayerDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.emoji.widget.EmojiTextView;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import autodagger.AutoInjector;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import com.nextcloud.talk.R;
|
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.application.NextcloudTalkApplication;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
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.database.UserEntity;
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||||
import com.nextcloud.talk.utils.AccountUtils;
|
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.DrawableUtils;
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
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.Single;
|
||||||
import io.reactivex.SingleObserver;
|
import io.reactivex.SingleObserver;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication.class)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
|
public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageMessageViewHolder<ChatMessage> {
|
||||||
|
|
||||||
|
private static String TAG = "MagicPreviewMessageViewHolder";
|
||||||
|
|
||||||
@BindView(R.id.messageText)
|
@BindView(R.id.messageText)
|
||||||
EmojiTextView messageText;
|
EmojiTextView messageText;
|
||||||
|
|
||||||
|
View progressBar;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Context context;
|
Context context;
|
||||||
|
|
||||||
@ -73,6 +94,7 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
|
|||||||
public MagicPreviewMessageViewHolder(View itemView) {
|
public MagicPreviewMessageViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
|
progressBar = itemView.findViewById(R.id.progress_bar);
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
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) {
|
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||||
// it's a preview for a Nextcloud share
|
String fileName = message.getSelectedIndividualHashMap().get("name");
|
||||||
messageText.setText(message.getSelectedIndividualHashMap().get("name"));
|
messageText.setText(fileName);
|
||||||
DisplayUtils.setClickableString(message.getSelectedIndividualHashMap().get("name"), message.getSelectedIndividualHashMap().get("link"), messageText);
|
|
||||||
if (message.getSelectedIndividualHashMap().containsKey("mimetype")) {
|
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 {
|
} else {
|
||||||
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get("path"), message.activeUser);
|
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get("path"), message.activeUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String accountString =
|
||||||
|
message.activeUser.getUsername() + "@" + message.activeUser.getBaseUrl().replace("https://", "").replace("http://", "");
|
||||||
|
|
||||||
image.setOnClickListener(v -> {
|
image.setOnClickListener(v -> {
|
||||||
|
String mimetype = message.getSelectedIndividualHashMap().get("mimetype");
|
||||||
String accountString =
|
if (isSupportedMimetype(mimetype)) {
|
||||||
message.activeUser.getUsername() + "@" + message.activeUser.getBaseUrl().replace("https://", "").replace("http://", "");
|
openOrDownloadFile(message);
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(message.getSelectedIndividualHashMap().get("link")));
|
openFileInFilesApp(message, accountString);
|
||||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(browserIntent);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||||
messageText.setText("GIPHY");
|
messageText.setText("GIPHY");
|
||||||
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", messageText);
|
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) {
|
private void fetchFileInformation(String url, UserEntity activeUser) {
|
||||||
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -374,6 +376,10 @@ public interface NcApi {
|
|||||||
@Url String url,
|
@Url String url,
|
||||||
@Body RequestBody body);
|
@Body RequestBody body);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
Call<ResponseBody> downloadFile(@Header("Authorization") String authorization,
|
||||||
|
@Url String url);
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
Observable<ChatOverallSingleMessage> deleteChatMessage(@Header("Authorization") String authorization,
|
Observable<ChatOverallSingleMessage> deleteChatMessage(@Header("Authorization") String authorization,
|
||||||
@Url String url);
|
@Url String url);
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
* Nextcloud Talk application
|
* Nextcloud Talk application
|
||||||
*
|
*
|
||||||
* @author Mario Danic
|
* @author Mario Danic
|
||||||
|
* @author Marcel Hibbe
|
||||||
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
* 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
|
* 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
|
* 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 (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
|
||||||
if (newMessagesCount != 0 && layoutManager != null) {
|
if (newMessagesCount != 0 && layoutManager != null) {
|
||||||
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() <
|
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) {
|
||||||
newMessagesCount) {
|
|
||||||
newMessagesCount = 0
|
newMessagesCount = 0
|
||||||
|
|
||||||
if (popupBubble != null && popupBubble!!.isShown) {
|
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
|
package com.nextcloud.talk.jobs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import autodagger.AutoInjector
|
import autodagger.AutoInjector
|
||||||
@ -45,6 +43,8 @@ import io.reactivex.schedulers.Schedulers
|
|||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -80,9 +80,9 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||||||
|
|
||||||
for (index in sourcefiles.indices) {
|
for (index in sourcefiles.indices) {
|
||||||
val sourcefileUri = Uri.parse(sourcefiles[index])
|
val sourcefileUri = Uri.parse(sourcefiles[index])
|
||||||
var filename = UriUtils.getFileName(sourcefileUri, context)
|
val filename = UriUtils.getFileName(sourcefileUri, context)
|
||||||
val requestBody = createRequestBody(sourcefileUri)
|
val requestBody = createRequestBody(sourcefileUri)
|
||||||
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody)
|
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody, sourcefileUri)
|
||||||
}
|
}
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
|
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
|
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(
|
ncApi.uploadFile(
|
||||||
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
ApiUtils.getCredentials(currentUser.username, currentUser.token),
|
||||||
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
|
ApiUtils.getUrlForFileUpload(currentUser.baseUrl, currentUser.userId, ncTargetpath, filename),
|
||||||
@ -128,10 +129,23 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
|||||||
|
|
||||||
override fun onComplete() {
|
override fun onComplete() {
|
||||||
shareFile(roomToken, currentUser, ncTargetpath, filename)
|
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?) {
|
private fun shareFile(roomToken: String?, currentUser: UserEntity, ncTargetpath: String?, filename: String?) {
|
||||||
val paths: MutableList<String> = ArrayList()
|
val paths: MutableList<String> = ArrayList()
|
||||||
paths.add("$ncTargetpath/$filename")
|
paths.add("$ncTargetpath/$filename")
|
||||||
|
@ -299,6 +299,10 @@ public class ApiUtils {
|
|||||||
return baseUrl + "/remote.php/dav/files/" + user + attachmentFolder + "/" + filename;
|
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) {
|
public static String getUrlForMessageDeletion(String baseUrl, String token, String messageId) {
|
||||||
return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token + "/" + messageId;
|
return baseUrl + ocsApiVersion + spreedApiVersion + "/chat/" + token + "/" + messageId;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ object UriUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (filename == null) {
|
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
|
filename = uri.path
|
||||||
val lastIndexOfSlash = filename!!.lastIndexOf('/')
|
val lastIndexOfSlash = filename!!.lastIndexOf('/')
|
||||||
if (lastIndexOfSlash != -1) {
|
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
|
~ Nextcloud Talk application
|
||||||
~
|
~
|
||||||
~ @author Mario Danic
|
~ @author Mario Danic
|
||||||
|
~ @author Marcel Hibbe
|
||||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
~ 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
|
~ 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
|
~ it under the terms of the GNU General Public License as published by
|
||||||
@ -48,16 +50,28 @@
|
|||||||
app:flexWrap="wrap"
|
app:flexWrap="wrap"
|
||||||
app:justifyContent="flex_end">
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<FrameLayout
|
||||||
android:id="@id/image"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:layout_flexGrow="1"
|
app:layout_flexGrow="1"
|
||||||
app:layout_wrapBefore="true"
|
app:layout_wrapBefore="true"
|
||||||
app:layout_alignSelf="flex_start"
|
app:layout_alignSelf="flex_start"
|
||||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
android:adjustViewBounds="true">
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@id/image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
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
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@id/messageText"
|
android:id="@id/messageText"
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
~ Nextcloud Talk application
|
~ Nextcloud Talk application
|
||||||
~
|
~
|
||||||
~ @author Mario Danic
|
~ @author Mario Danic
|
||||||
|
~ @author Marcel Hibbe
|
||||||
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
~ 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
|
~ 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
|
~ it under the terms of the GNU General Public License as published by
|
||||||
@ -40,17 +42,28 @@
|
|||||||
app:flexWrap="wrap"
|
app:flexWrap="wrap"
|
||||||
app:justifyContent="flex_end">
|
app:justifyContent="flex_end">
|
||||||
|
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
<FrameLayout
|
||||||
android:id="@id/image"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
app:layout_flexGrow="1"
|
app:layout_flexGrow="1"
|
||||||
app:layout_wrapBefore="true"
|
app:layout_wrapBefore="true"
|
||||||
app:layout_alignSelf="flex_start"
|
app:layout_alignSelf="flex_start"
|
||||||
app:actualImageScaleType="fitCenter"
|
android:adjustViewBounds="true">
|
||||||
tools:src="@tools:sample/backgrounds/scenic"/>
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@id/image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
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
|
<androidx.emoji.widget.EmojiTextView
|
||||||
android:id="@id/messageText"
|
android:id="@id/messageText"
|
||||||
@ -63,7 +76,8 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:layout_alignSelf="flex_start"
|
app:layout_alignSelf="flex_start"
|
||||||
app:layout_flexGrow="1"
|
app:layout_flexGrow="1"
|
||||||
app:layout_wrapBefore="true" />
|
app:layout_wrapBefore="true"
|
||||||
|
tools:text="Message" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@id/messageTime"
|
android:id="@id/messageTime"
|
||||||
@ -72,7 +86,8 @@
|
|||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:textColor="@color/warm_grey_four"
|
android:textColor="@color/warm_grey_four"
|
||||||
app:layout_alignSelf="center" />
|
app:layout_alignSelf="center"
|
||||||
|
tools:text="12:34:56" />
|
||||||
</com.google.android.flexbox.FlexboxLayout>
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</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">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="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 -->
|
<!-- Upload -->
|
||||||
<string name="nc_upload_local_file">Upload local file</string>
|
<string name="nc_upload_local_file">Upload local file</string>
|
||||||
<string name="nc_upload_from_cloud">Share from %1$s</string>
|
<string name="nc_upload_from_cloud">Share from %1$s</string>
|
||||||
@ -393,7 +397,7 @@
|
|||||||
<string name="scope_published_title">Published</string>
|
<string name="scope_published_title">Published</string>
|
||||||
<string name="scope_published_description">Synchronize to trusted servers and the global and public address book</string>
|
<string name="scope_published_description">Synchronize to trusted servers and the global and public address book</string>
|
||||||
<string name="scope_toggle">Scope toggle</string>
|
<string name="scope_toggle">Scope toggle</string>
|
||||||
|
|
||||||
<!-- App Bar -->
|
<!-- App Bar -->
|
||||||
<string name="appbar_search_in">Search in %s</string>
|
<string name="appbar_search_in">Search in %s</string>
|
||||||
|
|
||||||
@ -403,4 +407,5 @@
|
|||||||
<string name="nc_action_open_main_menu">Open main menu</string>
|
<string name="nc_action_open_main_menu">Open main menu</string>
|
||||||
<string name="failed_to_save">Failed to save %1$s</string>
|
<string name="failed_to_save">Failed to save %1$s</string>
|
||||||
<string name="selected_list_item">selected</string>
|
<string name="selected_list_item">selected</string>
|
||||||
|
<string name="filename_progress">%1$s (%2$d)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -133,6 +133,26 @@
|
|||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/white</item>
|
||||||
</style>
|
</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 -->
|
<!-- Launch screen -->
|
||||||
<style name="AppTheme.Launcher">
|
<style name="AppTheme.Launcher">
|
||||||
<item name="android:windowBackground">@drawable/launch_screen</item>
|
<item name="android:windowBackground">@drawable/launch_screen</item>
|
||||||
|
@ -19,5 +19,10 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<paths>
|
<paths>
|
||||||
<files-path name="files" path="/" />
|
<files-path
|
||||||
|
name="files"
|
||||||
|
path="/" />
|
||||||
|
<cache-path
|
||||||
|
name="cachedFiles"
|
||||||
|
path="/" />
|
||||||
</paths>
|
</paths>
|
||||||
|
Loading…
Reference in New Issue
Block a user