diff --git a/app/build.gradle b/app/build.gradle index 300f1eacb..3d6728361 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,7 +115,7 @@ dependencies { implementation 'com.google.android.material:material:1.1.0-alpha05' implementation 'androidx.exifinterface:exifinterface:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.preference:preference:1.1.0-rc01' + implementation 'androidx.preference:preference:1.1.0-alpha04' implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 2b0d4177f..243a6723b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -1248,6 +1248,10 @@ public final class ComposeActivity } private void readyStatus(final Status.Visibility visibility, final boolean sensitive) { + if (waitForMediaLatch.isEmpty()) { + onReadySuccess(visibility, sensitive); + return; + } finishingUploadDialog = ProgressDialog.show( this, getString(R.string.dialog_title_finishing_media_upload), getString(R.string.dialog_message_uploading_media), true, true); diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 5f16e8a95..2a57c485e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -422,8 +422,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } } - private void loadImage(MediaPreviewImageView imageView, String previewUrl, String description, - MetaData meta) { + private void loadImage(MediaPreviewImageView imageView, String previewUrl, MetaData meta) { if (TextUtils.isEmpty(previewUrl)) { Glide.with(imageView) .load(mediaPreviewUnloadedId) @@ -473,7 +472,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } if (!sensitive || showingContent) { - loadImage(imageView, previewUrl, description, attachments.get(i).getMeta()); + loadImage(imageView, previewUrl, attachments.get(i).getMeta()); } else { imageView.setImageResource(mediaPreviewUnloadedId); } @@ -546,6 +545,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { case GIFV: case VIDEO: return R.drawable.ic_videocam_24dp; + case AUDIO: + return R.drawable.ic_music_box_24dp; } } @@ -593,11 +594,14 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } private static CharSequence getAttachmentDescription(Context context, Attachment attachment) { + String duration = ""; + if(attachment.getMeta().getDuration() != null && attachment.getMeta().getDuration() > 0) { + duration = formatDuration(attachment.getMeta().getDuration()) + " "; + } if (TextUtils.isEmpty(attachment.getDescription())) { - return context - .getString(R.string.description_status_media_no_description_placeholder); + return duration + context.getString(R.string.description_status_media_no_description_placeholder); } else { - return attachment.getDescription(); + return duration + attachment.getDescription(); } } @@ -728,7 +732,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { setQuoteContainer(status.getQuote(), listener); List attachments = status.getAttachments(); boolean sensitive = status.isSensitive(); - if (mediaPreviewEnabled) { + if (mediaPreviewEnabled && !hasAudioAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.isShowingContent()); if (attachments.size() == 0) { @@ -776,6 +780,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } } + protected static boolean hasAudioAttachment(List attachments) { + for(Attachment attachment: attachments) { + if (attachment.getType() == Attachment.Type.AUDIO) { + return true; + } + } + return false; + } + private void setDescriptionForStatus(@NonNull StatusViewData.Concrete status) { Context context = itemView.getContext(); @@ -976,4 +989,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { return pollDescription.getContext().getString(R.string.poll_info_format, votesText, pollDurationInfo); } + private static String formatDuration(double durationInSeconds) { + int seconds = (int) Math.round(durationInSeconds) % 60; + int minutes = (int) durationInSeconds % 3600 / 60; + int hours = (int) durationInSeconds / 3600; + + return String.format("%d:%02d:%02d", hours, minutes, seconds); + } + } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index d77c90e09..4757b5676 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -85,7 +85,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder { setFavourited(status.getFavourited()); List attachments = status.getAttachments(); boolean sensitive = status.getSensitive(); - if(mediaPreviewEnabled) { + if(mediaPreviewEnabled && !hasAudioAttachment(attachments)) { setMediaPreviews(attachments, sensitive, listener, status.getShowingHiddenContent()); if (attachments.size() == 0) { diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt index 785b7c566..dc3732bcc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt @@ -42,6 +42,8 @@ data class Attachment( GIFV, @SerializedName("video") VIDEO, + @SerializedName("audio") + AUDIO, @SerializedName("unknown") UNKNOWN } @@ -53,6 +55,7 @@ data class Attachment( "\"image\"" -> Type.IMAGE "\"gifv\"" -> Type.GIFV "\"video\"" -> Type.VIDEO + "\"audio\"" -> Type.AUDIO else -> Type.UNKNOWN } } @@ -63,7 +66,8 @@ data class Attachment( */ @Parcelize data class MetaData ( - val focus: Focus? + val focus: Focus?, + val duration: Float? ) : Parcelable /** diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt b/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt index a3c775bc0..289a93fb7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt @@ -29,6 +29,6 @@ data class DeletedStatus( @SerializedName("created_at") val createdAt: Date ) { fun isEmpty(): Boolean { - return text == null && attachments == null; + return text == null && attachments == null } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/History.kt b/app/src/main/java/com/keylesspalace/tusky/entity/History.kt deleted file mode 100644 index b92975ba9..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/entity/History.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.keylesspalace.tusky.entity - -import com.google.gson.annotations.SerializedName - -data class History( - @field:SerializedName("day") - val day: String, - - @field:SerializedName("uses") - val uses: Int, - - @field:SerializedName("accounts") - val accounts: Int -) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 79f68faab..47f70e6fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -261,6 +261,7 @@ public class NotificationsFragment extends SFragment implements buttonFilter.setOnClickListener(v -> showFilterMenu()); if (notifications.isEmpty()) { + swipeRefreshLayout.setEnabled(false); sendFetchNotificationsRequest(null, null, FetchEnd.BOTTOM, -1); } else { progressBar.setVisibility(View.GONE); @@ -375,7 +376,6 @@ public class NotificationsFragment extends SFragment implements @Override public void onRefresh() { - swipeRefreshLayout.setEnabled(true); this.statusView.setVisibility(View.GONE); Either first = CollectionsKt.firstOrNull(this.notifications); String topId; @@ -946,7 +946,8 @@ public class NotificationsFragment extends SFragment implements if (notifications.size() == 0 && adapter.getItemCount() == 0) { this.statusView.setVisibility(View.VISIBLE); this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null); - + } else { + swipeRefreshLayout.setEnabled(true); } swipeRefreshLayout.setRefreshing(false); progressBar.setVisibility(View.GONE); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java index c7702a7a2..754b4dd96 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/SFragment.java @@ -339,7 +339,8 @@ public abstract class SFragment extends BaseFragment implements Injectable { switch (type) { case GIFV: case VIDEO: - case IMAGE: { + case IMAGE: + case AUDIO: { final List attachments = AttachmentViewData.list(actionable); final Intent intent = ViewMediaActivity.newIntent(getContext(), attachments, urlIndex); diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index fcc8ddae7..540469fac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -51,7 +51,8 @@ abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListen val fragment = when (attachment.type) { Attachment.Type.IMAGE -> ViewImageFragment() Attachment.Type.VIDEO, - Attachment.Type.GIFV -> ViewVideoFragment() + Attachment.Type.GIFV, + Attachment.Type.AUDIO -> ViewVideoFragment() else -> ViewImageFragment() // it probably won't show anything, but its better than crashing } fragment.arguments = arguments diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java index 6874337bd..70a084867 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/CountUpDownLatch.java @@ -44,4 +44,8 @@ public class CountUpDownLatch { wait(); } } + + public synchronized boolean isEmpty() { + return count == 0; + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/MultiListing.kt b/app/src/main/java/com/keylesspalace/tusky/util/MultiListing.kt deleted file mode 100644 index 550c1a934..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/util/MultiListing.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.keylesspalace.tusky.util - -import androidx.lifecycle.LiveData -import androidx.paging.PagedList - -/** - * Data class that is necessary for a UI to show a listing and interact w/ the rest of the system - */ -data class MultiListing( - val pagedLists: List>>, - // represents the network request status to show to the user - val networkState: LiveData, - // represents the refresh status to show to the user. Separate from networkState, this - // value is importantly only when refresh is requested. - val refreshState: LiveData, - // refreshes the whole data and fetches it from scratch. - val refresh: () -> Unit, - // retries any failed requests. - val retry: () -> Unit) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt b/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt index 19efb116e..65c8f6c08 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt @@ -34,7 +34,7 @@ fun deserialize(data: String?): Set { val ret = HashSet() data?.let { val array = JSONArray(data) - for (i in 0..(array.length() - 1)) { + for (i in 0 until array.length()) { val item = array.getString(i) val type = Notification.Type.byString(item) if (type != Notification.Type.UNKNOWN) diff --git a/app/src/main/res/drawable/ic_music_box_24dp.xml b/app/src/main/res/drawable/ic_music_box_24dp.xml new file mode 100644 index 000000000..c0243d935 --- /dev/null +++ b/app/src/main/res/drawable/ic_music_box_24dp.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index b3360fa01..98df4e943 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.41' + ext.kotlin_version = '1.3.50' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta05' - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta06' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } }