Merge branch 'mastodon_compat' into 'master'

Improve compatibility and add a shortcut for creating a post

See merge request pixeldroid/PixelDroid!432
This commit is contained in:
Matthieu 2022-06-21 16:52:53 +00:00
commit ad8cf02c1d
23 changed files with 180 additions and 76 deletions

View File

@ -0,0 +1,15 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="new_post"
android:enabled="true"
android:icon="@drawable/photo_camera"
android:shortcutShortLabel="@string/new_post_shortcut_short"
android:shortcutLongLabel="@string/new_post_shortcut_long">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="org.pixeldroid.app.debug"
android:targetClass="org.pixeldroid.app.postCreation.camera.CameraActivityShortcut" />
<categories android:name="android.shortcut.conversation" />
<capability-binding android:key="actions.intent.CREATE_MESSAGE" />
</shortcut>
</shortcuts>

View File

@ -35,7 +35,17 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="false"
android:theme="@style/AppTheme.NoActionBar" />
<activity android:name=".postCreation.camera.CameraActivity" />
<activity android:name=".postCreation.camera.CameraActivity"/>
<activity android:name=".postCreation.camera.CameraActivityShortcut"
android:exported = "true"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity " />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name=".posts.ReportActivity"
android:screenOrientation="sensorPortrait"
@ -46,31 +56,12 @@
android:exported="true"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<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>
@ -112,6 +103,8 @@
<meta-data
android:name="android.app.default_searchable"
android:value="org.pixeldroid.app.searchDiscover.SearchActivity" />
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".LoginActivity"

View File

@ -373,7 +373,7 @@ class MainActivity : BaseActivity() {
when(position){
0 -> R.drawable.ic_home_white_24dp
1 -> R.drawable.ic_search_white_24dp
2 -> R.drawable.ic_photo_camera_white_24dp
2 -> R.drawable.photo_camera
3 -> R.drawable.ic_heart
4 -> R.drawable.ic_filter_black_24dp
else -> throw IllegalArgumentException()

View File

@ -355,7 +355,7 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null
}
fun modifyAt(position: Int, data: Intent): Unit? {
val result: PhotoData = photoData.value?.get(position)?.run {
val result: PhotoData = photoData.value?.getOrNull(position)?.run {
if (video) {
val muted: Boolean = data.getBooleanExtra(VideoEditActivity.MUTED, false)
val videoStart: Float? = data.getFloatExtra(VideoEditActivity.VIDEO_START, -1f).let {

View File

@ -1,8 +1,13 @@
package org.pixeldroid.app.postCreation.camera
import android.content.Intent
import android.os.Bundle
import org.pixeldroid.app.utils.BaseActivity
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import org.pixeldroid.app.MainActivity
import org.pixeldroid.app.R
import org.pixeldroid.app.utils.BaseActivity
class CameraActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -20,4 +25,36 @@ class CameraActivity : BaseActivity() {
supportFragmentManager.beginTransaction()
.add(R.id.camera_activity_fragment, cameraFragment).commit()
}
}
/**
* Launch without arguments so that it will open the
* [org.pixeldroid.app.postCreation.PostCreationActivity] instead of "returning" to a non-existent
* [org.pixeldroid.app.postCreation.PostCreationActivity]
*/
class CameraActivityShortcut : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setTitle(R.string.new_post_shortcut_long)
val cameraFragment = CameraFragment()
supportFragmentManager.beginTransaction()
.add(R.id.camera_activity_fragment, cameraFragment).commit()
}
//Start a new MainActivity when "going back" on this activity
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
super.startActivity(intent)
}
}
return true
}
}

View File

@ -442,7 +442,7 @@ class CameraFragment : BaseFragment() {
.apply {
uris.forEach{
//Why are we using ClipData here? Because the FLAG_GRANT_READ_URI_PERMISSION
//needs to be applied to the URIs, and this flag flag only applies to the
//needs to be applied to the URIs, and this flag only applies to the
//Intent's data and any URIs specified in its ClipData.
if(clipData == null){
clipData = ClipData("", emptyArray(), ClipData.Item(it.toUri()))

View File

@ -91,7 +91,7 @@ class ImageCarousel(
}
if (position != RecyclerView.NO_POSITION && field != position) {
val thisProgress = data?.get(position)?.encodeProgress
val thisProgress = data?.getOrNull(position)?.encodeProgress
if (thisProgress != null) {
binding.encodeProgress.visibility = VISIBLE
binding.encodeInfoText.visibility = VISIBLE
@ -473,7 +473,7 @@ class ImageCarousel(
onScrollListener?.apply {
val position = snapHelper.getSnapPosition(recyclerView.layoutManager)
val carouselItem = data?.get(position)
val carouselItem = data?.getOrNull(position)
onScrollStateChanged(
recyclerView,
@ -564,7 +564,7 @@ class ImageCarousel(
}
fun updateProgress(progress: Int?, position: Int, error: Boolean){
data?.get(position)?.encodeProgress = progress
data?.getOrNull(position)?.encodeProgress = progress
if(currentPosition == position) {
if (progress == null) {
binding.encodeProgress.visibility = INVISIBLE

View File

@ -356,7 +356,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
status?.downloadImage(
binding.root.context,
status?.media_attachments?.get(binding.postPager.currentItem)?.url
status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url
?: "",
binding.root
)
@ -379,7 +379,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
status?.downloadImage(
binding.root.context,
status?.media_attachments?.get(binding.postPager.currentItem)?.url
status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url
?: "",
binding.root,
share = true,
@ -590,7 +590,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
class AlbumViewPagerAdapter(
private val media_attachments: List<Attachment>, private var sensitive: Boolean?,
private val opened: Boolean, //TODO if opened don't open again, and use PhotoView instead of shite
private val opened: Boolean,
) :
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {

View File

@ -103,10 +103,10 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
when (type) {
Notification.NotificationType.mention, Notification.NotificationType.favourite,
Notification.NotificationType.poll, Notification.NotificationType.reblog,
Notification.NotificationType.comment -> {
Notification.NotificationType.comment, Notification.NotificationType.status -> {
openPostFromNotification()
}
Notification.NotificationType.follow -> {
Notification.NotificationType.follow, Notification.NotificationType.follow_request -> {
openAccountFromNotification()
}
null -> return
@ -164,6 +164,16 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
)
Notification.NotificationType.poll ->
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
Notification.NotificationType.follow_request -> getStringAndDrawable(
context,
R.string.follow_request,
R.drawable.ic_follow
)
Notification.NotificationType.status -> getStringAndDrawable(
context,
R.string.status_notification,
R.drawable.ic_comment_empty
)
}
textView.text = format.format(username)
textView.setCompoundDrawablesWithIntrinsicBounds(

View File

@ -19,6 +19,8 @@ import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.snackbar.Snackbar
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityProfileBinding
@ -40,6 +42,8 @@ import org.pixeldroid.app.utils.openUrl
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.pixeldroid.app.databinding.ErrorLayoutBinding
import org.pixeldroid.app.postCreation.carousel.dpToPx
import org.pixeldroid.app.utils.BlurHashDecoder
import org.pixeldroid.app.utils.api.objects.Attachment
import retrofit2.HttpException
import java.io.IOException
@ -349,23 +353,39 @@ class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerVie
fun bind(post: Status) {
if(post.sensitive!!) {
ImageConverter.setSquareImageFromDrawable(
itemView,
AppCompatResources.getDrawable(itemView.context, R.drawable.ic_sensitive),
postPreview
)
} else {
ImageConverter.setSquareImageFromURL(itemView, post.getPostPreviewURL(), postPreview)
}
if(post.media_attachments?.size ?: 0 > 1) {
albumIcon.visibility = View.VISIBLE
} else {
if ((post.media_attachments?.size ?: 0) == 0){
//No media in this post, so put a little icon there
postPreview.scaleX = 0.3f
postPreview.scaleY = 0.3f
Glide.with(postPreview).load(R.drawable.ic_comment_empty).into(postPreview)
albumIcon.visibility = View.GONE
if(post.media_attachments?.get(0)?.type == Attachment.AttachmentType.video) {
videoIcon.visibility = View.VISIBLE
} else videoIcon.visibility = View.GONE
videoIcon.visibility = View.GONE
} else {
postPreview.scaleX = 1f
postPreview.scaleY = 1f
if (post.sensitive != false) {
Glide.with(postPreview)
.load(post.media_attachments?.firstOrNull()?.blurhash?.let {
BlurHashDecoder.blurHashBitmap(itemView.resources, it, 32, 32)
}
).placeholder(R.drawable.ic_sensitive).apply(RequestOptions().centerCrop())
.into(postPreview)
} else {
ImageConverter.setSquareImageFromURL(postPreview,
post.getPostPreviewURL(),
postPreview,
post.media_attachments?.firstOrNull()?.blurhash)
}
if ((post.media_attachments?.size ?: 0) > 1) {
albumIcon.visibility = View.VISIBLE
videoIcon.visibility = View.GONE
} else {
albumIcon.visibility = View.GONE
if (post.media_attachments?.getOrNull(0)?.type == Attachment.AttachmentType.video) {
videoIcon.visibility = View.VISIBLE
} else videoIcon.visibility = View.GONE
}
}
postPreview.setOnClickListener {

View File

@ -117,11 +117,11 @@ class SearchDiscoverFragment : BaseFragment() {
override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) {
val post = posts[position]
if(post?.media_attachments?.size ?: 0 > 1) {
if((post?.media_attachments?.size ?: 0) > 1) {
holder.albumIcon.visibility = View.VISIBLE
} else {
holder.albumIcon.visibility = View.GONE
if(post?.media_attachments?.get(0)?.type == Attachment.AttachmentType.video) {
if(post?.media_attachments?.getOrNull(0)?.type == Attachment.AttachmentType.video) {
holder.videoIcon.visibility = View.VISIBLE
} else holder.videoIcon.visibility = View.GONE

View File

@ -42,6 +42,8 @@ data class Account(
val fields: List<Field>? = emptyList(),
val bot: Boolean? = false,
val source: Source? = null,
val suspended: Boolean? = null,
val mute_expires_at: Instant? = null, //ISO 8601 Datetime
) : Serializable, FeedContent {
companion object {
const val ACCOUNT_TAG = "AccountTag"

View File

@ -10,14 +10,16 @@ data class Attachment(
val preview_url: String? = "", //URL
//Optional attributes
val remote_url: String? = null, //URL
val text_url: String? = null, //URL
val meta: Meta?,
val description: String? = null,
val blurhash: String? = null
val blurhash: String? = null,
//Deprecated attributes
val text_url: String? = null, //URL
) : Serializable {
enum class AttachmentType {
enum class AttachmentType: Serializable {
unknown, image, gifv, video, audio
}

View File

@ -17,7 +17,8 @@ data class Card(
val width: Int? = null,
val height: Int? = null,
val image: String? = null, //URL
val embed_url: String? = null //URL
val embed_url: String? = null, //URL
val blurhash: String? = null,
) : Serializable {
enum class CardType {
link, photo, video, rich

View File

@ -1,6 +1,7 @@
package org.pixeldroid.app.utils.api.objects
import org.pixeldroid.app.utils.validDomain
import java.io.Serializable
/*
See https://nodeinfo.diaspora.software/schema.html and https://pixelfed.social/api/nodeinfo/2.0.json
@ -14,7 +15,7 @@ data class NodeInfo (
val protocols: List<String>?,
val openRegistrations: Boolean?,
val metadata: PixelfedMetadata?,
){
): Serializable {
/**
* Check if this NodeInfo has the fields we need or if we also need to look into the
* /api/v1/instance endpoint
@ -31,16 +32,16 @@ data class NodeInfo (
data class Software(
val name: String?,
val version: String?
)
): Serializable
data class PixelfedMetadata(
val nodeName: String?,
val software: Software?,
val config: PixelfedConfig
){
): Serializable {
data class Software(
val homepage: String?,
val repo: String?
)
): Serializable
}
data class PixelfedConfig(
val open_registration: Boolean?,
@ -48,8 +49,8 @@ data class NodeInfo (
val activitypub: ActivityPub?,
val features: Features?,
val site: Site?
){
data class Uploader(
): Serializable {
data class Uploader (
val max_photo_size: String?,
val max_caption_length: String?,
val album_limit: String?,
@ -58,35 +59,34 @@ data class NodeInfo (
val optimize_video: Boolean?,
val media_types: String?,
val enforce_account_limit: Boolean?
)
): Serializable
data class ActivityPub(
val enabled: Boolean?,
val remote_follow: Boolean?
)
): Serializable
data class Features(
val mobile_apis: Boolean?,
val circles: Boolean?,
val stories: Boolean?,
val video: Boolean?
)
): Serializable
data class Site(
val name: String?,
val domain: String?,
val url: String?,
val description: String?
)
): Serializable
}
}
data class NodeInfoJRD(
val links: List<Link>
){
): Serializable {
data class Link(
val rel: String?,
val href: String?
)
): Serializable
}

View File

@ -36,8 +36,8 @@ data class Notification(
//TODO do we find this approach acceptable? Preferable to a semi-duplicate NotificationDataBaseEntity?
override var user_id: String,
override var instance_uri: String,
): FeedContent, FeedContentDatabase {
): FeedContent, FeedContentDatabase, Serializable {
enum class NotificationType: Serializable {
follow, mention, reblog, favourite, poll, comment
follow, follow_request, mention, reblog, favourite, poll, status, comment //comment is Pixelfed-specific?
}
}

View File

@ -18,5 +18,5 @@ data class Poll (
data class Option(
val title: String?,
val votes_count: Int? //null if result not published yet
)
): Serializable
}

View File

@ -11,7 +11,8 @@ data class Tag(
val name: String,
val url: String,
//Optional attributes
val history: List<History>? = emptyList()) : Serializable, FeedContent {
val history: List<History>? = emptyList()
) : Serializable, FeedContent {
//needed to be a FeedContent, this inheritance is a bit fickle. Do not use.
override val id: String
get() = "tag"

View File

@ -1,9 +1,11 @@
package org.pixeldroid.app.utils.api.objects
import java.io.Serializable
data class Token(
val access_token: String?,
val refresh_token: String?,
val token_type: String?,
val scope: String?,
val created_at: Int?
)
val created_at: Int? //UNIX timestamp
): Serializable

View File

@ -172,11 +172,11 @@ class NotificationsWorker(
val builder = NotificationCompat.Builder(applicationContext, makeChannelId(uniqueUserId, notification.type))
.setSmallIcon(
when (notification.type) {
follow -> R.drawable.ic_follow
follow, follow_request -> R.drawable.ic_follow
mention -> R.drawable.mention_at_24dp
reblog -> R.drawable.ic_reblog
favourite -> R.drawable.ic_like_full
comment -> R.drawable.ic_comment_empty
comment, status -> R.drawable.ic_comment_empty
poll -> R.drawable.poll
null -> R.drawable.ic_comment_empty
}
@ -193,6 +193,8 @@ class NotificationsWorker(
favourite -> R.string.liked_notification
poll -> R.string.poll_notification
null -> R.string.other_notification
follow_request -> R.string.follow_request
status -> R.string.status_notification
}
).format(username)
}

View File

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#FFFFFF"
<vector android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>

View File

@ -270,4 +270,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="encode_progress">Encode %1$d%%</string>
<string name="select_video_range">Select what to keep of the video</string>
<string name="still_encoding">One or more videos are still encoding. Wait for them to finish before uploading</string>
<string name="new_post_shortcut_long">Create new post</string>
<string name="new_post_shortcut_short">New post</string>
<string name="follow_request">%1$s requests to follow you</string>
<string name="status_notification">%1$s created a post</string>
</resources>

View File

@ -0,0 +1,15 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="new_post"
android:enabled="true"
android:icon="@drawable/photo_camera"
android:shortcutShortLabel="@string/new_post_shortcut_short"
android:shortcutLongLabel="@string/new_post_shortcut_long">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="org.pixeldroid.app"
android:targetClass="org.pixeldroid.app.postCreation.camera.CameraActivityShortcut" />
<categories android:name="android.shortcut.conversation" />
<capability-binding android:key="actions.intent.CREATE_MESSAGE" />
</shortcut>
</shortcuts>