Merge branch 'dev' into Refactor_VideoPlayerUi
This commit is contained in:
commit
161007fe92
212 changed files with 3239 additions and 703 deletions
|
|
@ -172,7 +172,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
|
||||
// Start the worker which is checking all conditions
|
||||
// and eventually searching for a new version.
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(app);
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(app, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,18 @@ package org.schabi.newpipe
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.grack.nanojson.JsonParser
|
||||
import com.grack.nanojson.JsonParserException
|
||||
import org.schabi.newpipe.extractor.downloader.Response
|
||||
|
|
@ -42,26 +44,40 @@ class NewVersionWorker(
|
|||
versionCode: Int
|
||||
) {
|
||||
if (BuildConfig.VERSION_CODE >= versionCode) {
|
||||
if (inputData.getBoolean(IS_MANUAL, false)) {
|
||||
// Show toast stating that the app is up-to-date if the update check was manual.
|
||||
ContextCompat.getMainExecutor(applicationContext).execute {
|
||||
Toast.makeText(
|
||||
applicationContext, R.string.app_update_unavailable_toast,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
val app = App.getApp()
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val pendingIntent = PendingIntentCompat.getActivity(app, 0, intent, 0)
|
||||
val channelId = app.getString(R.string.app_update_notification_channel_id)
|
||||
val notificationBuilder = NotificationCompat.Builder(app, channelId)
|
||||
val pendingIntent = PendingIntentCompat.getActivity(
|
||||
applicationContext, 0, intent, 0
|
||||
)
|
||||
val channelId = applicationContext.getString(R.string.app_update_notification_channel_id)
|
||||
val notificationBuilder = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(
|
||||
app.getString(R.string.app_update_notification_content_text) +
|
||||
" " + versionName
|
||||
.setContentIntent(pendingIntent)
|
||||
.setContentTitle(
|
||||
applicationContext.getString(R.string.app_update_available_notification_title)
|
||||
)
|
||||
val notificationManager = NotificationManagerCompat.from(app)
|
||||
.setContentText(
|
||||
applicationContext.getString(
|
||||
R.string.app_update_available_notification_text, versionName
|
||||
)
|
||||
)
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||
notificationManager.notify(2000, notificationBuilder.build())
|
||||
}
|
||||
|
||||
|
|
@ -72,12 +88,14 @@ class NewVersionWorker(
|
|||
return
|
||||
}
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
// Check if the last request has happened a certain time ago
|
||||
// to reduce the number of API requests.
|
||||
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
|
||||
if (!isLastUpdateCheckExpired(expiry)) {
|
||||
return
|
||||
if (!inputData.getBoolean(IS_MANUAL, false)) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
// Check if the last request has happened a certain time ago
|
||||
// to reduce the number of API requests.
|
||||
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
|
||||
if (!isLastUpdateCheckExpired(expiry)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
|
|
@ -120,43 +138,42 @@ class NewVersionWorker(
|
|||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
try {
|
||||
return try {
|
||||
checkNewVersion()
|
||||
Result.success()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e)
|
||||
return Result.failure()
|
||||
Result.failure()
|
||||
} catch (e: ReCaptchaException) {
|
||||
Log.e(TAG, "ReCaptchaException should never happen here.", e)
|
||||
return Result.failure()
|
||||
Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEBUG = MainActivity.DEBUG
|
||||
private val TAG = NewVersionWorker::class.java.simpleName
|
||||
private const val NEWPIPE_API_URL = "https://newpipe.net/api/data.json"
|
||||
private const val IS_MANUAL = "isManual"
|
||||
|
||||
/**
|
||||
* Start a new worker which
|
||||
* checks if all conditions for performing a version check are met,
|
||||
* fetches the API endpoint [.NEWPIPE_API_URL] containing info
|
||||
* about the latest NewPipe version
|
||||
* and displays a notification about ana available update.
|
||||
* Start a new worker which checks if all conditions for performing a version check are met,
|
||||
* fetches the API endpoint [.NEWPIPE_API_URL] containing info about the latest NewPipe
|
||||
* version and displays a notification about an available update if one is available.
|
||||
* <br></br>
|
||||
* Following conditions need to be met, before data is request from the server:
|
||||
* Following conditions need to be met, before data is requested from the server:
|
||||
*
|
||||
* * The app is signed with the correct signing key (by TeamNewPipe / schabi).
|
||||
* If the signing key differs from the one used upstream, the update cannot be installed.
|
||||
* * The user enabled searching for and notifying about updates in the settings.
|
||||
* * The app did not recently check for updates.
|
||||
* We do not want to make unnecessary connections and DOS our servers.
|
||||
*
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enqueueNewVersionCheckingWork(context: Context) {
|
||||
val workRequest: WorkRequest =
|
||||
OneTimeWorkRequest.Builder(NewVersionWorker::class.java).build()
|
||||
fun enqueueNewVersionCheckingWork(context: Context, isManual: Boolean) {
|
||||
val workRequest = OneTimeWorkRequestBuilder<NewVersionWorker>()
|
||||
.setInputData(workDataOf(IS_MANUAL to isManual))
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1561,9 +1561,9 @@ public final class VideoDetailFragment
|
|||
binding.detailSubChannelThumbnailView.setVisibility(View.GONE);
|
||||
|
||||
if (!isEmpty(info.getSubChannelName())) {
|
||||
displayBothUploaderAndSubChannel(info);
|
||||
displayBothUploaderAndSubChannel(info, activity);
|
||||
} else if (!isEmpty(info.getUploaderName())) {
|
||||
displayUploaderAsSubChannel(info);
|
||||
displayUploaderAsSubChannel(info, activity);
|
||||
} else {
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||
|
|
@ -1676,23 +1676,42 @@ public final class VideoDetailFragment
|
|||
noVideoStreams ? R.drawable.ic_headset_shadow : R.drawable.ic_play_arrow_shadow);
|
||||
}
|
||||
|
||||
private void displayUploaderAsSubChannel(final StreamInfo info) {
|
||||
private void displayUploaderAsSubChannel(final StreamInfo info, final Context context) {
|
||||
binding.detailSubChannelTextView.setText(info.getUploaderName());
|
||||
binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
|
||||
binding.detailSubChannelTextView.setSelected(true);
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
|
||||
if (info.getUploaderSubscriberCount() > -1) {
|
||||
binding.detailUploaderTextView.setText(
|
||||
Localization.shortSubscriberCount(context, info.getUploaderSubscriberCount()));
|
||||
binding.detailUploaderTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
|
||||
private void displayBothUploaderAndSubChannel(final StreamInfo info, final Context context) {
|
||||
binding.detailSubChannelTextView.setText(info.getSubChannelName());
|
||||
binding.detailSubChannelTextView.setVisibility(View.VISIBLE);
|
||||
binding.detailSubChannelTextView.setSelected(true);
|
||||
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
|
||||
final StringBuilder subText = new StringBuilder();
|
||||
if (!isEmpty(info.getUploaderName())) {
|
||||
binding.detailUploaderTextView.setText(
|
||||
subText.append(
|
||||
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
|
||||
}
|
||||
if (info.getUploaderSubscriberCount() > -1) {
|
||||
if (subText.length() > 0) {
|
||||
subText.append(Localization.DOT_SEPARATOR);
|
||||
}
|
||||
subText.append(
|
||||
Localization.shortSubscriberCount(context, info.getUploaderSubscriberCount()));
|
||||
}
|
||||
|
||||
if (subText.length() > 0) {
|
||||
binding.detailUploaderTextView.setText(subText);
|
||||
binding.detailUploaderTextView.setVisibility(View.VISIBLE);
|
||||
binding.detailUploaderTextView.setSelected(true);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -215,8 +215,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||
final Resources resources = activity.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||
width += (24 * resources.getDisplayMetrics().density);
|
||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
|
||||
/ (double) width);
|
||||
final int spanCount = Math.floorDiv(resources.getDisplayMetrics().widthPixels, width);
|
||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||
lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount));
|
||||
return lm;
|
||||
|
|
|
|||
|
|
@ -252,10 +252,11 @@ public final class InfoItemDialog {
|
|||
* @return the current {@link Builder} instance
|
||||
*/
|
||||
public Builder addEnqueueEntriesIfNeeded() {
|
||||
if (PlayerHolder.getInstance().isPlayQueueReady()) {
|
||||
final PlayerHolder holder = PlayerHolder.getInstance();
|
||||
if (holder.isPlayQueueReady()) {
|
||||
addEntry(StreamDialogDefaultEntry.ENQUEUE);
|
||||
|
||||
if (PlayerHolder.getInstance().getQueueSize() > 1) {
|
||||
if (holder.getQueuePosition() < holder.getQueueSize() - 1) {
|
||||
addEntry(StreamDialogDefaultEntry.ENQUEUE_NEXT);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,8 +104,7 @@ public abstract class BaseLocalListFragment<I, N> extends BaseStateFragment<I>
|
|||
final Resources resources = activity.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width);
|
||||
width += (24 * resources.getDisplayMetrics().density);
|
||||
final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels
|
||||
/ (double) width);
|
||||
final int spanCount = Math.floorDiv(resources.getDisplayMetrics().widthPixels, width);
|
||||
final GridLayoutManager lm = new GridLayoutManager(activity, spanCount);
|
||||
lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount));
|
||||
return lm;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionS
|
|||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupAddNewGridItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupAddNewItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem
|
||||
|
|
@ -49,6 +48,7 @@ import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
|||
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
|
||||
import org.schabi.newpipe.local.subscription.item.GroupsHeader
|
||||
import org.schabi.newpipe.local.subscription.item.Header
|
||||
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||
|
|
@ -312,7 +312,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
groupAdapter.add(this)
|
||||
}
|
||||
|
||||
subscriptionsSection.setPlaceholder(EmptyPlaceholderItem())
|
||||
subscriptionsSection.setPlaceholder(ImportSubscriptionsHintPlaceholderItem())
|
||||
subscriptionsSection.setHideWhenEmpty(true)
|
||||
|
||||
groupAdapter.add(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.
|
|||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.SubscriptionsPickerScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.ImportSubscriptionsHintPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
import org.schabi.newpipe.util.DeviceUtils
|
||||
|
|
@ -338,7 +338,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
|||
|
||||
if (subscriptions.isEmpty()) {
|
||||
subscriptionEmptyFooter.clear()
|
||||
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||
subscriptionEmptyFooter.add(ImportSubscriptionsHintPlaceholderItem())
|
||||
} else {
|
||||
subscriptionEmptyFooter.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import com.xwray.groupie.viewbinding.BindableItem
|
|||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.databinding.ListEmptyViewBinding
|
||||
|
||||
class EmptyPlaceholderItem : BindableItem<ListEmptyViewBinding>() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
/**
|
||||
* When there are no subscriptions, show a hint to the user about how to import subscriptions
|
||||
*/
|
||||
class ImportSubscriptionsHintPlaceholderItem : BindableItem<ListEmptyViewBinding>() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view_subscriptions
|
||||
override fun bind(viewBinding: ListEmptyViewBinding, position: Int) {}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||
override fun initializeViewBinding(view: View) = ListEmptyViewBinding.bind(view)
|
||||
|
|
@ -92,6 +92,13 @@ public final class PlayerHolder {
|
|||
return player.getPlayQueue().size();
|
||||
}
|
||||
|
||||
public int getQueuePosition() {
|
||||
if (player == null || player.getPlayQueue() == null) {
|
||||
return 0;
|
||||
}
|
||||
return player.getPlayQueue().getIndex();
|
||||
}
|
||||
|
||||
public void setListener(@Nullable final PlayerServiceExtendedEventListener newListener) {
|
||||
listener = newListener;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.widget.ImageView;
|
|||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.graphics.BitmapCompat;
|
||||
import androidx.core.math.MathUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
|
|
@ -91,8 +92,8 @@ public final class SeekbarPreviewThumbnailHelper {
|
|||
final float scaleFactor = (float) newWidth / srcWidth;
|
||||
final int newHeight = (int) (srcBitmap.getHeight() * scaleFactor);
|
||||
|
||||
currentSeekbarPreviewThumbnail.setImageBitmap(
|
||||
Bitmap.createScaledBitmap(srcBitmap, newWidth, newHeight, true));
|
||||
currentSeekbarPreviewThumbnail.setImageBitmap(BitmapCompat.createScaledBitmap(srcBitmap,
|
||||
newWidth, newHeight, null, true));
|
||||
} catch (final Exception ex) {
|
||||
Log.e(TAG, "Failed to resize and set seekbar preview thumbnail", ex);
|
||||
currentSeekbarPreviewThumbnail.setVisibility(View.GONE);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.core.graphics.BitmapCompat;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.math.MathUtils;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
|
@ -470,10 +471,11 @@ public abstract class VideoPlayerUi extends PlayerUi implements SeekBar.OnSeekBa
|
|||
}
|
||||
|
||||
final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(thumbnail);
|
||||
final Bitmap endScreenBitmap = Bitmap.createScaledBitmap(
|
||||
final Bitmap endScreenBitmap = BitmapCompat.createScaledBitmap(
|
||||
thumbnail,
|
||||
(int) (thumbnail.getWidth() / (thumbnail.getHeight() / endScreenHeight)),
|
||||
(int) endScreenHeight,
|
||||
null,
|
||||
true);
|
||||
|
||||
if (DEBUG) {
|
||||
|
|
|
|||
|
|
@ -16,25 +16,17 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
|
|||
.apply();
|
||||
|
||||
if (checkForUpdates) {
|
||||
checkNewVersionNow();
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(requireContext(), true);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private final Preference.OnPreferenceClickListener manualUpdateClick = preference -> {
|
||||
Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show();
|
||||
checkNewVersionNow();
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(requireContext(), true);
|
||||
return true;
|
||||
};
|
||||
|
||||
private void checkNewVersionNow() {
|
||||
// Search for updates immediately when update checks are enabled.
|
||||
// Reset the expire time. This is necessary to check for an update immediately.
|
||||
defaultPreferences.edit()
|
||||
.putLong(getString(R.string.update_expiry_key), 0).apply();
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
addPreferencesFromResourceRegistry();
|
||||
|
|
|
|||
|
|
@ -652,7 +652,7 @@ public class WebMWriter implements Closeable {
|
|||
|
||||
final int offset = withLength ? 1 : 0;
|
||||
final byte[] buffer = new byte[offset + length];
|
||||
final long marker = (long) Math.floor((length - 1f) / 8f);
|
||||
final long marker = Math.floorDiv(length - 1, 8);
|
||||
|
||||
int shift = 0;
|
||||
for (int i = length - 1; i >= 0; i--, shift += 8) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.graphics.Bitmap;
|
|||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.BitmapCompat;
|
||||
|
||||
import com.squareup.picasso.Cache;
|
||||
import com.squareup.picasso.LruCache;
|
||||
|
|
@ -139,21 +140,23 @@ public final class PicassoHelper {
|
|||
.getDimension(R.dimen.player_notification_thumbnail_width),
|
||||
source.getWidth());
|
||||
|
||||
final Bitmap result = Bitmap.createScaledBitmap(
|
||||
final Bitmap result = BitmapCompat.createScaledBitmap(
|
||||
source,
|
||||
(int) notificationThumbnailWidth,
|
||||
(int) (source.getHeight()
|
||||
/ (source.getWidth() / notificationThumbnailWidth)),
|
||||
null,
|
||||
true);
|
||||
|
||||
if (result == source) {
|
||||
if (result == source || !result.isMutable()) {
|
||||
// create a new mutable bitmap to prevent strange crashes on some
|
||||
// devices (see #4638)
|
||||
final Bitmap copied = Bitmap.createScaledBitmap(
|
||||
final Bitmap copied = BitmapCompat.createScaledBitmap(
|
||||
source,
|
||||
(int) notificationThumbnailWidth - 1,
|
||||
(int) (source.getHeight() / (source.getWidth()
|
||||
/ (notificationThumbnailWidth - 1))),
|
||||
null,
|
||||
true);
|
||||
source.recycle();
|
||||
return copied;
|
||||
|
|
|
|||
|
|
@ -314,7 +314,10 @@ public final class ShareUtils {
|
|||
}
|
||||
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
|
||||
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
if (Build.VERSION.SDK_INT < 33) {
|
||||
// Android 13 has its own "copied to clipboard" dialog
|
||||
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -693,7 +693,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
|||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
Utility.copyToClipboard(mContext, result);
|
||||
ShareUtils.copyToClipboard(mContext, result);
|
||||
notificationManager.cancel(HASH_NOTIFICATION_ID);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -192,18 +192,6 @@ public class Utility {
|
|||
}
|
||||
}
|
||||
|
||||
public static void copyToClipboard(Context context, String str) {
|
||||
ClipboardManager cm = ContextCompat.getSystemService(context, ClipboardManager.class);
|
||||
|
||||
if (cm == null) {
|
||||
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
cm.setPrimaryClip(ClipData.newPlainText("text", str));
|
||||
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public static String checksum(final StoredFileHelper source, final int algorithmId)
|
||||
throws IOException {
|
||||
ByteString byteString;
|
||||
|
|
@ -248,10 +236,10 @@ public class Utility {
|
|||
return number < 10 ? ("0" + number) : String.valueOf(number);
|
||||
}
|
||||
|
||||
public static String stringifySeconds(double seconds) {
|
||||
int h = (int) Math.floor(seconds / 3600);
|
||||
int m = (int) Math.floor((seconds - (h * 3600)) / 60);
|
||||
int s = (int) (seconds - (h * 3600) - (m * 60));
|
||||
public static String stringifySeconds(final long seconds) {
|
||||
final int h = (int) Math.floorDiv(seconds, 3600);
|
||||
final int m = (int) Math.floorDiv(seconds - (h * 3600L), 60);
|
||||
final int s = (int) (seconds - (h * 3600) - (m * 60));
|
||||
|
||||
String str = "";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue