Merge branch 'master' into 'profile_perf_fix'
# Conflicts: # app/build.gradle # app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt # build.gradle
This commit is contained in:
commit
d49e0f58ff
@ -28,8 +28,8 @@ android {
|
||||
applicationId "org.pixeldroid.app"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 31
|
||||
versionCode 7
|
||||
versionName "1.0.beta7"
|
||||
versionCode 8
|
||||
versionName "1.0.beta8"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
@ -128,6 +128,9 @@ dependencies {
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.4.1'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||
implementation 'androidx.media2:media2-widget:1.2.1'
|
||||
implementation 'androidx.media2:media2-player:1.2.1'
|
||||
|
||||
|
||||
// Use the most recent version of CameraX
|
||||
def cameraX_version = '1.1.0-beta02'
|
||||
|
104
app/licenses.yml
104
app/licenses.yml
@ -852,4 +852,106 @@
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/room#2.4.1
|
||||
url: https://developer.android.com/jetpack/androidx/releases/room#2.4.1
|
||||
- artifact: androidx.window:window:+
|
||||
name: window
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/window#1.0.0
|
||||
- artifact: com.android.support:animated-vector-drawable:+
|
||||
name: animated-vector-drawable
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:appcompat-v7:+
|
||||
name: appcompat-v7
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-annotations:+
|
||||
name: support-annotations
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-compat:+
|
||||
name: support-compat
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-core-ui:+
|
||||
name: support-core-ui
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-core-utils:+
|
||||
name: support-core-utils
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-fragment:+
|
||||
name: support-fragment
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-media-compat:+
|
||||
name: support-media-compat
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-v4:+
|
||||
name: support-v4
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: com.android.support:support-vector-drawable:+
|
||||
name: support-vector-drawable
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: androidx.media2:media2-widget:+
|
||||
name: media2-widget
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/media2
|
||||
- artifact: androidx.palette:palette:+
|
||||
name: palette
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
- artifact: androidx.media2:media2-player:+
|
||||
name: media2-player
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/media2
|
||||
- artifact: androidx.media2:media2-session:+
|
||||
name: media2-session
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/media2
|
||||
- artifact: androidx.media2:media2-common:+
|
||||
name: media2-common
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/media2
|
||||
- artifact: androidx.media2:media2-exoplayer:+
|
||||
name: media2-exoplayer
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx/releases/media2
|
||||
|
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
@ -44,6 +44,13 @@
|
||||
public *;
|
||||
}
|
||||
|
||||
-keepclassmembers class org.pixeldroid.app.settings.licenseObjects.* { *; }
|
||||
|
||||
-keep public enum org.pixeldroid.app.settings.licenseObjects.*$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# preserve line numbers for crash reporting
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
@ -22,6 +22,11 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:replace="android:allowBackup">
|
||||
<activity
|
||||
android:name=".posts.MediaViewerActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity android:name=".postCreation.camera.CameraActivity" />
|
||||
<activity
|
||||
android:name=".posts.ReportActivity"
|
||||
@ -30,20 +35,38 @@
|
||||
<activity android:name=".postCreation.photoEdit.PhotoEditActivity" />
|
||||
<activity
|
||||
android:name=".postCreation.PostCreationActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true">
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".profile.FollowsActivity"
|
||||
@ -69,11 +92,11 @@
|
||||
tools:ignore="LockedOrientationActivity" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/AppTheme.Launcher"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true">
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@ -86,11 +109,11 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true">
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@ -104,16 +127,16 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true"/>
|
||||
tools:ignore="LockedOrientationActivity" />
|
||||
<activity
|
||||
android:name=".searchDiscover.SearchActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:exported="true">
|
||||
tools:ignore="LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
@ -142,7 +165,6 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
File diff suppressed because one or more lines are too long
@ -50,11 +50,12 @@ import kotlin.math.ceil
|
||||
private const val TAG = "Post Creation Activity"
|
||||
|
||||
data class PhotoData(
|
||||
var imageUri: Uri,
|
||||
var size: Long,
|
||||
var uploadId: String? = null,
|
||||
var progress: Int? = null,
|
||||
var imageDescription: String? = null,
|
||||
var imageUri: Uri,
|
||||
var size: Long,
|
||||
var uploadId: String? = null,
|
||||
var progress: Int? = null,
|
||||
var imageDescription: String? = null,
|
||||
var video: Boolean,
|
||||
)
|
||||
|
||||
class PostCreationActivity : BaseActivity() {
|
||||
@ -85,7 +86,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
intent.clipData?.let { addPossibleImages(it) }
|
||||
|
||||
val carousel: ImageCarousel = binding.carousel
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri, video = it.video) })
|
||||
carousel.layoutCarouselCallback = {
|
||||
if(it){
|
||||
// Became a carousel
|
||||
@ -138,7 +139,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
binding.removePhotoButton.setOnClickListener {
|
||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||
photoData.removeAt(currentPosition)
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
|
||||
binding.addPhotoButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
@ -164,16 +165,17 @@ class PostCreationActivity : BaseActivity() {
|
||||
}
|
||||
for (i in 0 until count) {
|
||||
clipData.getItemAt(i).uri.let {
|
||||
val size = it.getSize()
|
||||
photoData.add(PhotoData(imageUri = it, size = size))
|
||||
val sizeAndVideoPair: Pair<Long, Boolean> = it.getSizeAndVideoValidate()
|
||||
photoData.add(PhotoData(imageUri = it, size = sizeAndVideoPair.first, video = sizeAndVideoPair.second))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the file of the Uri, and opens a dialog in case it is too big.
|
||||
* Returns the size of the file of the Uri, and whether it is a video,
|
||||
* and opens a dialog in case it is too big or in case the file is unsupported.
|
||||
*/
|
||||
private fun Uri.getSize(): Long {
|
||||
private fun Uri.getSizeAndVideoValidate(): Pair<Long, Boolean> {
|
||||
val size: Long =
|
||||
if (toString().startsWith("content")) {
|
||||
contentResolver.query(this, null, null, null, null)
|
||||
@ -191,22 +193,24 @@ class PostCreationActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
|
||||
val type = contentResolver.getType(this)
|
||||
val isVideo = type?.startsWith("video/") == true
|
||||
|
||||
if(isVideo && !instance.videoEnabled){
|
||||
AlertDialog.Builder(this@PostCreationActivity).apply {
|
||||
setMessage(R.string.video_not_supported)
|
||||
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
|
||||
if (sizeInkBytes > instance.maxPhotoSize || sizeInkBytes > instance.maxVideoSize) {
|
||||
val maxSize = when {
|
||||
instance.maxPhotoSize != instance.maxVideoSize -> {
|
||||
val type = contentResolver.getType(this)
|
||||
if (type?.startsWith("video/") == true) {
|
||||
instance.maxVideoSize
|
||||
} else instance.maxPhotoSize
|
||||
}
|
||||
else -> instance.maxPhotoSize
|
||||
}
|
||||
val maxSize = if (isVideo) instance.maxVideoSize else instance.maxPhotoSize
|
||||
AlertDialog.Builder(this@PostCreationActivity).apply {
|
||||
setMessage(getString(R.string.size_exceeds_instance_limit, photoData.size + 1, sizeInkBytes, maxSize))
|
||||
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
return size
|
||||
return Pair(size, isVideo)
|
||||
}
|
||||
|
||||
private val addPhotoResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
@ -214,7 +218,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
result.data?.clipData?.let {
|
||||
addPossibleImages(it)
|
||||
}
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
|
||||
} else if (result.resultCode != Activity.RESULT_CANCELED) {
|
||||
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@ -306,8 +310,8 @@ class PostCreationActivity : BaseActivity() {
|
||||
*/
|
||||
private fun upload() {
|
||||
enableButton(false)
|
||||
binding.uploadProgressBar.visibility = View.VISIBLE
|
||||
binding.uploadCompletedTextview.visibility = View.INVISIBLE
|
||||
binding.uploadProgressBar.visibility = VISIBLE
|
||||
binding.uploadCompletedTextview.visibility = INVISIBLE
|
||||
binding.removePhotoButton.isEnabled = false
|
||||
binding.editPhotoButton.isEnabled = false
|
||||
binding.addPhotoButton.isEnabled = false
|
||||
@ -429,21 +433,30 @@ class PostCreationActivity : BaseActivity() {
|
||||
val position: Int = result.data!!.getIntExtra(PhotoEditActivity.PICTURE_POSITION, 0)
|
||||
photoData.getOrNull(position)?.apply {
|
||||
imageUri = result.data!!.getStringExtra(PhotoEditActivity.PICTURE_URI)!!.toUri()
|
||||
size = imageUri.getSize()
|
||||
val (imageSize, imageVideo) = imageUri.getSizeAndVideoValidate()
|
||||
size = imageSize
|
||||
video = imageVideo
|
||||
progress = null
|
||||
uploadId = null
|
||||
} ?: Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
||||
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription, it.video) })
|
||||
} else if(result?.resultCode != Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun edit(position: Int) {
|
||||
val intent = Intent(this, PhotoEditActivity::class.java)
|
||||
.putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
|
||||
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
|
||||
editResultContract.launch(intent)
|
||||
if(photoData[position].video){
|
||||
AlertDialog.Builder(this).apply {
|
||||
setMessage(R.string.video_edit_not_yet_supported)
|
||||
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
} else {
|
||||
val intent = Intent(this, PhotoEditActivity::class.java)
|
||||
.putExtra(PhotoEditActivity.PICTURE_URI, photoData[position].imageUri)
|
||||
.putExtra(PhotoEditActivity.PICTURE_POSITION, position)
|
||||
editResultContract.launch(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,11 @@ package org.pixeldroid.app.postCreation
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.RelativeLayout
|
||||
|
||||
internal class SquareLayout(context: Context, attrs: AttributeSet) :
|
||||
RelativeLayout(context, attrs) {
|
||||
FrameLayout(context, attrs) {
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
|
||||
|
@ -41,6 +41,7 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.properties.Delegates
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.utils.BaseFragment
|
||||
|
||||
private const val ANIMATION_FAST_MILLIS = 50L
|
||||
private const val ANIMATION_SLOW_MILLIS = 100L
|
||||
@ -48,7 +49,7 @@ private const val ANIMATION_SLOW_MILLIS = 100L
|
||||
/**
|
||||
* Camera fragment
|
||||
*/
|
||||
class CameraFragment : Fragment() {
|
||||
class CameraFragment : BaseFragment() {
|
||||
|
||||
private lateinit var container: ConstraintLayout
|
||||
|
||||
@ -314,10 +315,15 @@ class CameraFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setupUploadImage() {
|
||||
val videoEnabled: Boolean = db.instanceDao().getInstance(db.userDao().getActiveUser()!!.instance_uri).videoEnabled
|
||||
var mimeTypes: Array<String> = arrayOf("image/*")
|
||||
if(videoEnabled) mimeTypes += "video/*"
|
||||
|
||||
// Listener for button used to view the most recent photo
|
||||
binding.photoViewButton.setOnClickListener {
|
||||
Intent().apply {
|
||||
type = "image/*"
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "*/*"
|
||||
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
|
@ -3,13 +3,17 @@ package org.pixeldroid.app.postCreation.carousel
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.posts.MediaViewerActivity
|
||||
|
||||
|
||||
class CarouselAdapter(
|
||||
@ -26,6 +30,8 @@ class CarouselAdapter(
|
||||
|
||||
class MyViewHolder(itemView: View, imageViewId: Int) : RecyclerView.ViewHolder(itemView) {
|
||||
var img: ImageView = itemView.findViewById(imageViewId)
|
||||
// Null if not relevant
|
||||
val videoIndicator: ImageButton? = itemView.findViewById(R.id.videoIndicator)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
@ -60,10 +66,21 @@ class CarouselAdapter(
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
if(carousel) {
|
||||
if (carousel) {
|
||||
holder.img.scaleType = imageScaleType
|
||||
holder.videoIndicator?.setOnClickListener{
|
||||
with(dataList[position]) {
|
||||
MediaViewerActivity.openActivity(
|
||||
holder.itemView.context,
|
||||
imageUrl.toString(),
|
||||
caption
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.videoIndicator?.visibility = if (dataList[position].video) VISIBLE else GONE
|
||||
|
||||
dataList.elementAtOrNull(position)?.let {
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(it.imageUrl)
|
||||
@ -83,7 +100,6 @@ class CarouselAdapter(
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import android.net.Uri
|
||||
|
||||
data class CarouselItem constructor(
|
||||
val imageUrl: Uri,
|
||||
val caption: String? = null
|
||||
) {
|
||||
constructor(imageUrl: Uri) : this(imageUrl, null)
|
||||
}
|
||||
val caption: String? = null,
|
||||
val video: Boolean
|
||||
)
|
@ -49,7 +49,7 @@ class ImageCarousel(
|
||||
var indicator: CircleIndicator2? = null
|
||||
set(newIndicator) {
|
||||
indicator?.apply {
|
||||
// if we remove it form the view, then the caption textView constraint won't work.
|
||||
// if we remove it from the view, then the caption textView constraint won't work.
|
||||
this.visibility = View.GONE
|
||||
|
||||
isBuiltInIndicator = false
|
||||
|
@ -0,0 +1,102 @@
|
||||
package org.pixeldroid.app.posts
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.media.AudioManager.STREAM_MUSIC
|
||||
import android.os.Bundle
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.media.AudioAttributesCompat
|
||||
import androidx.media2.common.MediaMetadata
|
||||
import androidx.media2.common.UriMediaItem
|
||||
import androidx.media2.player.MediaPlayer
|
||||
import org.pixeldroid.app.databinding.ActivityMediaviewerBinding
|
||||
import org.pixeldroid.app.utils.BaseActivity
|
||||
|
||||
class MediaViewerActivity : BaseActivity() {
|
||||
|
||||
private lateinit var mediaPlayer: MediaPlayer
|
||||
private lateinit var binding: ActivityMediaviewerBinding
|
||||
|
||||
companion object {
|
||||
const val VIDEO_URL_TAG = "video_url_mediavieweractivity"
|
||||
const val VIDEO_DESCRIPTION_TAG = "video_description_mediavieweractivity"
|
||||
|
||||
fun openActivity(context: Context, url: String?, description: String?){
|
||||
val intent = Intent(context, MediaViewerActivity::class.java)
|
||||
intent.putExtra(VIDEO_URL_TAG, url)
|
||||
intent.putExtra(VIDEO_DESCRIPTION_TAG, description)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMediaviewerBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val uri: String = intent.getStringExtra(VIDEO_URL_TAG).orEmpty()
|
||||
val description: String? = intent.getStringExtra(VIDEO_DESCRIPTION_TAG)
|
||||
|
||||
val mediaItem: UriMediaItem = UriMediaItem.Builder(uri.toUri()).build()
|
||||
mediaItem.metadata = MediaMetadata.Builder()
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, description ?: "")
|
||||
.build()
|
||||
|
||||
mediaPlayer = MediaPlayer(this)
|
||||
mediaPlayer.setMediaItem(mediaItem)
|
||||
|
||||
binding.videoView.mediaControlView?.setOnFullScreenListener{ view, fullscreen ->
|
||||
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
|
||||
if (!fullscreen) {
|
||||
// Configure the behavior of the hidden system bars
|
||||
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
// Hide both the status bar and the navigation bar
|
||||
windowInsetsController?.show(WindowInsetsCompat.Type.systemBars())
|
||||
supportActionBar?.show()
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
} else {
|
||||
// Configure the behavior of the hidden system bars
|
||||
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
// Hide both the status bar and the navigation bar
|
||||
windowInsetsController?.hide(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
requestedOrientation =
|
||||
if (mediaPlayer.videoSize.height < mediaPlayer.videoSize.width) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
supportActionBar?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Configure audio
|
||||
mediaPlayer.setAudioAttributes(AudioAttributesCompat.Builder()
|
||||
.setLegacyStreamType(STREAM_MUSIC)
|
||||
.setUsage(AudioAttributesCompat.USAGE_MEDIA)
|
||||
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE)
|
||||
.build()
|
||||
)
|
||||
|
||||
mediaPlayer.prepare()
|
||||
|
||||
binding.videoView.setPlayer(mediaPlayer)
|
||||
|
||||
// Start actually playing the video
|
||||
mediaPlayer.play()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mediaPlayer.pause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mediaPlayer.close()
|
||||
}
|
||||
}
|
@ -37,6 +37,9 @@ import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.VIDEO_DESCRIPTION_TAG
|
||||
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.VIDEO_URL_TAG
|
||||
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import kotlin.math.roundToInt
|
||||
@ -594,6 +597,7 @@ private class AlbumViewPagerAdapter(private val media_attachments: List<Attachme
|
||||
override fun getItemCount() = media_attachments.size
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
media_attachments[position].apply {
|
||||
val video = type == Attachment.AttachmentType.video
|
||||
val blurhashBitMap = blurhash?.let {
|
||||
BlurHashDecoder.blurHashBitmap(
|
||||
holder.binding.root.resources,
|
||||
@ -603,16 +607,28 @@ private class AlbumViewPagerAdapter(private val media_attachments: List<Attachme
|
||||
)
|
||||
}
|
||||
if (sensitive == false) {
|
||||
val imageUrl = if(video) preview_url else url
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter()
|
||||
.placeholder(blurhashBitMap)
|
||||
.load(url).into(holder.image)
|
||||
.load(imageUrl).into(holder.image)
|
||||
} else {
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter()
|
||||
.load(blurhashBitMap).into(holder.image)
|
||||
}
|
||||
|
||||
holder.videoPlayButton.visibility = if(video) View.VISIBLE else View.GONE
|
||||
|
||||
if(video){
|
||||
holder.videoPlayButton.setOnClickListener {
|
||||
openActivity(holder.binding.root.context, url, description)
|
||||
}
|
||||
holder.image.setOnClickListener {
|
||||
openActivity(holder.binding.root.context, url, description)
|
||||
}
|
||||
}
|
||||
|
||||
val description = description
|
||||
.orEmpty()
|
||||
.ifEmpty { holder.binding.root.context.getString(R.string.no_description) }
|
||||
@ -633,5 +649,6 @@ private class AlbumViewPagerAdapter(private val media_attachments: List<Attachme
|
||||
|
||||
class ViewHolder(val binding: AlbumImageViewBinding) : RecyclerView.ViewHolder(binding.root){
|
||||
val image: ImageView = binding.imageImageView
|
||||
val videoPlayButton: ImageView = binding.videoPlayButton
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
@ -31,6 +32,7 @@ import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
import org.pixeldroid.app.utils.api.objects.Status
|
||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||
import org.pixeldroid.app.utils.notificationsWorker.makeChannelGroupId
|
||||
|
||||
|
||||
/**
|
||||
@ -64,6 +66,16 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
with(NotificationManagerCompat.from(requireContext())) {
|
||||
// Cancel account notification group
|
||||
db.userDao().getActiveUser()?.let {
|
||||
cancel( makeChannelGroupId(it).hashCode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View Holder for a [Notification] RecyclerView list item.
|
||||
*/
|
||||
|
@ -38,6 +38,7 @@ import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import org.pixeldroid.app.utils.openUrl
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
@ -340,6 +341,7 @@ class ProfileViewModelFactory @ExperimentalPagingApi constructor(
|
||||
class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val postPreview: ImageView = binding.postPreview
|
||||
private val albumIcon: ImageView = binding.albumIcon
|
||||
private val videoIcon: ImageView = binding.videoIcon
|
||||
|
||||
fun bind(post: Status) {
|
||||
|
||||
@ -352,11 +354,14 @@ class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerVie
|
||||
} else {
|
||||
ImageConverter.setSquareImageFromURL(itemView, post.getPostPreviewURL(), postPreview)
|
||||
}
|
||||
|
||||
if(post.media_attachments?.size ?: 0 > 1) {
|
||||
albumIcon.visibility = View.VISIBLE
|
||||
} else {
|
||||
albumIcon.visibility = View.GONE
|
||||
if(post.media_attachments?.get(0)?.type == Attachment.AttachmentType.video) {
|
||||
videoIcon.visibility = View.VISIBLE
|
||||
} else videoIcon.visibility = View.GONE
|
||||
|
||||
}
|
||||
|
||||
postPreview.setOnClickListener {
|
||||
|
@ -8,4 +8,5 @@ import org.pixeldroid.app.R
|
||||
class ProfilePostViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
|
||||
val postPreview: ImageView = postView.findViewById(R.id.postPreview)
|
||||
val albumIcon: ImageView = postView.findViewById(R.id.albumIcon)
|
||||
val videoIcon: ImageView = postView.findViewById(R.id.albumIcon)
|
||||
}
|
@ -19,6 +19,7 @@ import org.pixeldroid.app.utils.api.objects.Status
|
||||
import org.pixeldroid.app.posts.PostActivity
|
||||
import org.pixeldroid.app.utils.BaseFragment
|
||||
import org.pixeldroid.app.utils.ImageConverter
|
||||
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||
import org.pixeldroid.app.utils.bindingLifecycleAware
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
@ -120,6 +121,10 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
holder.albumIcon.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.albumIcon.visibility = View.GONE
|
||||
if(post?.media_attachments?.get(0)?.type == Attachment.AttachmentType.video) {
|
||||
holder.videoIcon.visibility = View.VISIBLE
|
||||
} else holder.videoIcon.visibility = View.GONE
|
||||
|
||||
}
|
||||
ImageConverter.setSquareImageFromURL(holder.postView, post?.getPostPreviewURL(), holder.postPreview, post?.media_attachments?.firstOrNull()?.blurhash)
|
||||
holder.postPreview.setOnClickListener {
|
||||
|
@ -4,7 +4,10 @@ import android.os.Bundle
|
||||
import com.google.gson.Gson
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.databinding.OpenSourceLicenseBinding
|
||||
import org.pixeldroid.app.settings.licenseObjects.Libraries
|
||||
import org.pixeldroid.app.settings.licenseObjects.OpenSourceItem
|
||||
import org.pixeldroid.app.utils.BaseActivity
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
/**
|
||||
* Displays licenses for all app dependencies. JSON is
|
||||
@ -30,13 +33,10 @@ class LicenseActivity: BaseActivity() {
|
||||
private fun setupRecyclerView() {
|
||||
val text: String = applicationContext.assets.open("licenses.json")
|
||||
.bufferedReader().use { it.readText() }
|
||||
|
||||
val listObj: List<OpenSourceItem> = Gson().fromJson(text, Libraries::class.java).libraries
|
||||
|
||||
val adapter = OpenSourceLicenseAdapter()
|
||||
val adapter = OpenSourceLicenseAdapter(listObj)
|
||||
binding.openSourceLicenseRecyclerView.adapter = adapter
|
||||
|
||||
|
||||
adapter.updateList(listObj)
|
||||
|
||||
}
|
||||
}
|
@ -7,18 +7,11 @@ import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.pixeldroid.app.databinding.OpenSourceItemBinding
|
||||
import org.pixeldroid.app.settings.licenseObjects.OpenSourceItem
|
||||
|
||||
class OpenSourceLicenseAdapter :
|
||||
class OpenSourceLicenseAdapter(private val openSourceItems: List<OpenSourceItem>) :
|
||||
RecyclerView.Adapter<OpenSourceLicenseAdapter.OpenSourceLicenceViewHolder>() {
|
||||
|
||||
private var openSourceItems: List<OpenSourceItem> = emptyList()
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun updateList(newOpenSourceItems: List<OpenSourceItem>) {
|
||||
openSourceItems = newOpenSourceItems
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OpenSourceLicenceViewHolder
|
||||
{
|
||||
val itemBinding = OpenSourceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
@ -67,15 +60,4 @@ class OpenSourceLicenseAdapter :
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
data class OpenSourceItem(
|
||||
val libraryName: String?,
|
||||
val copyrightHolder: String?,
|
||||
val url: String?,
|
||||
val license: String?,
|
||||
val licenseUrl: String?,
|
||||
)
|
||||
|
||||
data class Libraries(
|
||||
val libraries: List<OpenSourceItem>
|
||||
)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.pixeldroid.app.settings.licenseObjects
|
||||
|
||||
data class Libraries(
|
||||
val libraries: List<OpenSourceItem>
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package org.pixeldroid.app.settings.licenseObjects
|
||||
|
||||
|
||||
data class OpenSourceItem(
|
||||
val libraryName: String?,
|
||||
val copyrightHolder: String?,
|
||||
val url: String?,
|
||||
val license: String?,
|
||||
val licenseUrl: String?,
|
||||
)
|
@ -73,6 +73,7 @@ fun bitmapFromUri(contentResolver: ContentResolver, uri: Uri?): Bitmap =
|
||||
)
|
||||
{ decoder, _, _ -> decoder.isMutableRequired = true }
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
|
||||
modifyOrientation(bitmap!!, contentResolver, uri!!)
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import org.pixeldroid.app.utils.api.objects.Notification
|
||||
PublicFeedStatusDatabaseEntity::class,
|
||||
Notification::class
|
||||
],
|
||||
version = 4
|
||||
version = 5
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -39,4 +39,9 @@ val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||
database.execSQL("DELETE FROM publicPosts")
|
||||
database.execSQL("DELETE FROM notifications")
|
||||
}
|
||||
}
|
||||
val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE instances ADD COLUMN videoEnabled INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEF
|
||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_PHOTO_SIZE
|
||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_TOOT_CHARS
|
||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_VIDEO_SIZE
|
||||
import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_VIDEO_ENABLED
|
||||
import org.pixeldroid.app.utils.normalizeDomain
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
@ -33,13 +34,14 @@ fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser:
|
||||
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
|
||||
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
|
||||
InstanceDatabaseEntity(
|
||||
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||
title = metadata.config.site.name!!,
|
||||
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
||||
//Pixelfed doesn't distinguish between max photo and video size
|
||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
||||
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT
|
||||
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||
title = metadata.config.site.name!!,
|
||||
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
||||
// Pixelfed doesn't distinguish between max photo and video size
|
||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
||||
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT,
|
||||
videoEnabled = metadata.config.features?.video ?: DEFAULT_VIDEO_ENABLED
|
||||
)
|
||||
} ?: instance?.run {
|
||||
InstanceDatabaseEntity(
|
||||
|
@ -8,6 +8,9 @@ interface InstanceDao {
|
||||
@Query("SELECT * FROM instances")
|
||||
fun getAll(): List<InstanceDatabaseEntity>
|
||||
|
||||
@Query("SELECT * FROM instances WHERE uri=:instanceUri LIMIT 1")
|
||||
fun getInstance(instanceUri: String): InstanceDatabaseEntity
|
||||
|
||||
/**
|
||||
* Insert an instance, if it already exists return -1
|
||||
*/
|
||||
|
@ -14,6 +14,8 @@ data class InstanceDatabaseEntity (
|
||||
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||
// Is video functionality enabled on this instance?
|
||||
var videoEnabled: Boolean = DEFAULT_VIDEO_ENABLED,
|
||||
) {
|
||||
companion object{
|
||||
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
||||
@ -23,5 +25,6 @@ data class InstanceDatabaseEntity (
|
||||
const val DEFAULT_MAX_PHOTO_SIZE = 8000
|
||||
const val DEFAULT_MAX_VIDEO_SIZE = 40000
|
||||
const val DEFAULT_ALBUM_LIMIT = 4
|
||||
const val DEFAULT_VIDEO_ENABLED = true
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.pixeldroid.app.utils.db.MIGRATION_3_4
|
||||
import org.pixeldroid.app.utils.db.MIGRATION_4_5
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@ -17,6 +18,7 @@ class DatabaseModule(private val context: Context) {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java, "pixeldroid"
|
||||
).addMigrations(MIGRATION_3_4).allowMainThreadQueries().build()
|
||||
).addMigrations(MIGRATION_3_4).addMigrations(MIGRATION_4_5)
|
||||
.allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import androidx.work.WorkerParameters
|
||||
import org.pixeldroid.app.MainActivity
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.posts.PostActivity
|
||||
import org.pixeldroid.app.posts.fromHtml
|
||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
@ -71,8 +72,7 @@ class NotificationsWorker(
|
||||
)
|
||||
|
||||
while (!newNotifications.isNullOrEmpty()
|
||||
&& newNotifications.map { it.created_at ?: Instant.MIN }
|
||||
.maxOrNull()!! > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
&& newNotifications.maxOf { it.created_at ?: Instant.MIN } > previouslyLatestNotification?.created_at ?: Instant.MIN
|
||||
) {
|
||||
// Add to db
|
||||
val filteredNewNotifications: List<Notification> = newNotifications.filter {
|
||||
@ -83,6 +83,12 @@ class NotificationsWorker(
|
||||
|
||||
db.notificationDao().insertAll(filteredNewNotifications)
|
||||
|
||||
|
||||
//If multiple notifications, show summary of them
|
||||
if(filteredNewNotifications.size > 1){
|
||||
showNotificationSummary(filteredNewNotifications, uniqueUserId)
|
||||
}
|
||||
|
||||
// Launch new notifications
|
||||
filteredNewNotifications.forEach {
|
||||
showNotification(it, user, uniqueUserId)
|
||||
@ -106,6 +112,39 @@ class NotificationsWorker(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun showNotificationSummary(notifications: List<Notification>, uniqueUserId: String) {
|
||||
val content = joinNames(
|
||||
applicationContext,
|
||||
notifications.mapNotNull { it.account?.getDisplayName() }
|
||||
)
|
||||
|
||||
val title: String = applicationContext.resources.getQuantityString(
|
||||
R.plurals.notification_title_summary,
|
||||
notifications.size,
|
||||
notifications.size
|
||||
)
|
||||
|
||||
val groupBuilder = NotificationCompat.Builder(applicationContext, makeChannelId(uniqueUserId, null))
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setGroupSummary(true)
|
||||
.setAutoCancel(true)
|
||||
.setGroup(uniqueUserId)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(content))
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(SHOW_NOTIFICATION_TAG, true)
|
||||
}, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
)
|
||||
|
||||
with(NotificationManagerCompat.from(applicationContext)) {
|
||||
notify(uniqueUserId.hashCode(), groupBuilder.build())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
notification: Notification,
|
||||
user: UserDatabaseEntity,
|
||||
@ -166,7 +205,7 @@ class NotificationsWorker(
|
||||
.setAutoCancel(true)
|
||||
|
||||
if (notification.type == mention || notification.type == comment || notification.type == poll){
|
||||
builder.setContentText(notification.status?.content)
|
||||
builder.setContentText(notification.status?.content?.let { fromHtml(it) })
|
||||
}
|
||||
|
||||
builder.setGroup(uniqueUserId)
|
||||
@ -237,7 +276,8 @@ fun makeNotificationChannels(context: Context, handle: String, channelGroupId: S
|
||||
|
||||
/**
|
||||
* [channelGroupId] is the id used to uniquely identify the group: for us it is a unique id
|
||||
* identifying a user consisting of the concatenation of the instance uri and user id.
|
||||
* identifying a user consisting of the concatenation of the instance uri and user id
|
||||
* (see [makeChannelGroupId]).
|
||||
*/
|
||||
private fun makeChannelId(channelGroupId: String, type: Notification.NotificationType?): String =
|
||||
(channelGroupId + (type ?: NotificationsWorker.otherNotificationType)).hashCode().toString()
|
||||
@ -264,4 +304,27 @@ fun removeNotificationChannelsFromAccount(context: Context, user: UserDatabaseEn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BidiFormatter.unicodeWrap is insufficient in some cases (see Tusky#1921)
|
||||
* So we force isolation manually
|
||||
* https://unicode.org/reports/tr9/#Explicit_Directional_Isolates
|
||||
*/
|
||||
fun CharSequence.unicodeWrap(): String = "\u2068${this}\u2069"
|
||||
|
||||
private fun joinNames(context: Context, notifications: List<String>): String {
|
||||
return when {
|
||||
notifications.size > 3 -> {
|
||||
context.getString(R.string.notification_summary_large).format(
|
||||
*notifications.subList(0, 3).map { it.unicodeWrap() }.toTypedArray(),
|
||||
notifications.size - 3
|
||||
)
|
||||
}
|
||||
else -> context.getString( when(notifications.size) {
|
||||
2 -> R.string.notification_summary_small
|
||||
else /* ==3 */-> R.string.notification_summary_medium
|
||||
}).format(*notifications.map { it.unicodeWrap() }.toTypedArray())
|
||||
}
|
||||
}
|
18
app/src/main/res/drawable/notification_icon.xml
Normal file
18
app/src/main/res/drawable/notification_icon.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="4"
|
||||
android:scaleY="4"
|
||||
android:translateX="-53"
|
||||
android:translateY="-53">
|
||||
<path
|
||||
android:pathData="M23.0332,30.2725L27.5781,30.2725C31.8595,30.2725 35.3302,26.9088 35.3302,22.7596C35.3302,18.6103 31.8595,15.2467 27.5781,15.2467L21.0185,15.2467C18.5485,15.2467 16.5461,17.1872 16.5461,19.581L16.5461,36.451L23.0332,30.2725Z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
</group>
|
||||
</vector>
|
9
app/src/main/res/drawable/play_circle_filled.xml
Normal file
9
app/src/main/res/drawable/play_circle_filled.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,16.5v-9l6,4.5 -6,4.5z"/>
|
||||
</vector>
|
12
app/src/main/res/layout/activity_mediaviewer.xml
Normal file
12
app/src/main/res/layout/activity_mediaviewer.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.media2.widget.VideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -10,6 +10,7 @@
|
||||
android:id="@+id/addPhotoSquare"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/add_photo_button"
|
||||
|
@ -14,4 +14,15 @@
|
||||
android:adjustViewBounds="true"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/videoPlayButton"
|
||||
android:visibility="gone"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/play_circle_filled"
|
||||
android:contentDescription="@string/play_video" />
|
||||
|
||||
</FrameLayout>
|
@ -38,9 +38,24 @@
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible"
|
||||
tools:visibility="gone"
|
||||
android:contentDescription="@string/post_is_album" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/videoIcon"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:foreground="@drawable/play_circle_filled"
|
||||
android:foregroundGravity="center"
|
||||
android:foregroundTint="#FFFFFF"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/post_is_video" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.gridlayout.widget.GridLayout>
|
||||
|
||||
|
@ -1,15 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.pixeldroid.app.postCreation.SquareLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/galleryImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/post_image"
|
||||
android:padding="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@string/post_image" />
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/videoIndicator"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_margin="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/play_video"
|
||||
android:src="@drawable/play_circle_filled"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
</org.pixeldroid.app.postCreation.SquareLayout>
|
@ -1,9 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/img"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/videoIndicator"
|
||||
android:visibility="gone"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/play_circle_filled"
|
||||
android:contentDescription="@string/play_video" />
|
||||
|
||||
</FrameLayout>
|
@ -64,6 +64,13 @@
|
||||
<string name="poll_notification_channel">"Polls"</string>
|
||||
<string name="other_notification_channel">"Other"</string>
|
||||
|
||||
<plurals name="notification_title_summary">
|
||||
<item quantity="one">"%d new notification"</item>
|
||||
<item quantity="other">"%d new notifications"</item>
|
||||
</plurals>
|
||||
<string name="notification_summary_large">%1$s, %2$s, %3$s and %4$d others</string>
|
||||
<string name="notification_summary_medium">%1$s, %2$s, and %3$s</string>
|
||||
<string name="notification_summary_small">%1$s and %2$s</string>
|
||||
|
||||
|
||||
<!-- Login page -->
|
||||
@ -107,6 +114,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<string name="no_media_description">Add a media description here…</string>
|
||||
<string name="total_exceeds_album_limit">You chose more images than the maximum your server allows (%1$s). Images beyond the limit have been ignored.</string>
|
||||
<string name="size_exceeds_instance_limit">Size of image number %1$d in the album exceeds the maximum size allowed by the instance (%2$d kB but the limit is %3$d kB). You might not be able to upload it.</string>
|
||||
<string name="video_not_supported">"The server you are using doesn't support video uploads, you might not be able to upload videos included in this post"</string>
|
||||
<string name="upload_error">Error code returned by server: %1$d</string>
|
||||
|
||||
|
||||
@ -165,6 +173,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<string name="add_comment">Add a comment</string>
|
||||
<string name="submit_comment">Submit comment</string>
|
||||
<string name="post_is_album">This post is an album</string>
|
||||
<string name="post_is_video">This post is a video</string>
|
||||
|
||||
<!-- Profile page -->
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">"%d\nPost"</item>
|
||||
@ -250,4 +260,6 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<string name="login_notifications">Couldn\'t fetch latest notifications</string>
|
||||
<string name="no_camera_permission">Camera permission not granted, grant the permission in settings if you want to let PixelDroid use the camera</string>
|
||||
<string name="no_storage_permission">Storage permission not granted, grant the permission in settings if you want to let PixelDroid show the thumbnail</string>
|
||||
<string name="play_video">Play video</string>
|
||||
<string name="video_edit_not_yet_supported">Video editing is not yet supported</string>
|
||||
</resources>
|
2
fastlane/metadata/android/de/changelogs/7.txt
Normal file
2
fastlane/metadata/android/de/changelogs/7.txt
Normal file
@ -0,0 +1,2 @@
|
||||
* Aktualisierung von Übersetzungen
|
||||
* Auswählen von nicht lokal gespeicherten Dateien (z.B. Nextcloud) beim Upload eingeführt
|
4
fastlane/metadata/android/en-US/changelogs/8.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/8.txt
Normal file
@ -0,0 +1,4 @@
|
||||
* Add support for video playback and upload
|
||||
* Notification improvements
|
||||
* Translation updates
|
||||
* Update dependencies
|
Loading…
x
Reference in New Issue
Block a user