Tweaks to QR code and media viewer
This commit is contained in:
parent
b8a5346631
commit
1cdc6f4fcf
|
@ -5,8 +5,10 @@ import android.animation.Animator;
|
|||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.DownloadManager;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
|
@ -44,11 +46,12 @@ import android.view.LayoutInflater;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
@ -80,6 +83,7 @@ import org.joinmastodon.android.googleservices.barcodescanner.Barcode;
|
|||
import org.joinmastodon.android.googleservices.barcodescanner.BarcodeScanner;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.drawables.FancyQrCodeDrawable;
|
||||
import org.joinmastodon.android.ui.drawables.RadialParticleSystemDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
@ -95,6 +99,9 @@ import java.util.Map;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
|
@ -113,6 +120,8 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
private View codeContainer;
|
||||
private View particleAnimContainer;
|
||||
private Animator currentTransition;
|
||||
private View saveBtn;
|
||||
private TextView saveBtnText;
|
||||
|
||||
private String accountID;
|
||||
private Account account;
|
||||
|
@ -168,6 +177,7 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
|
@ -196,7 +206,8 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
TextView username=content.findViewById(R.id.username);
|
||||
TextView domain=content.findViewById(R.id.domain);
|
||||
View share=content.findViewById(R.id.share_btn);
|
||||
Button save=content.findViewById(R.id.save_btn);
|
||||
saveBtn=content.findViewById(R.id.save_btn);
|
||||
saveBtnText=content.findViewById(R.id.save_text);
|
||||
View cornerAnimContainer=content.findViewById(R.id.corner_animation_container);
|
||||
particleAnimContainer=content.findViewById(R.id.particle_animation_container);
|
||||
codeContainer=content.findViewById(R.id.code_container);
|
||||
|
@ -217,12 +228,16 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.share_user)));
|
||||
});
|
||||
save.setOnClickListener(v->saveCodeAsFile());
|
||||
saveBtn.setOnClickListener(v->saveCodeAsFile());
|
||||
|
||||
cornerAnimContainer.setBackground(new AnimatedCornersDrawable(themeWrapper));
|
||||
int particleColor=UiUtils.getThemeColor(themeWrapper, R.attr.colorM3Primary);
|
||||
particles=new RadialParticleSystemDrawable(5000, 200, (particleColor & 0xFFFFFF) | 0x80000000, particleColor & 0xFFFFFF, V.dp(65), V.dp(50), getResources().getDisplayMetrics().density);
|
||||
particleAnimContainer.setBackground(particles);
|
||||
content.setOnTouchListener(new TouchDismissListener());
|
||||
|
||||
int buttonExtraWidth=saveBtn.getPaddingLeft()+saveBtn.getPaddingRight()+saveBtnText.getCompoundDrawablesRelative()[0].getIntrinsicWidth()+saveBtnText.getCompoundDrawablePadding();
|
||||
saveBtn.getLayoutParams().width=(int)Math.max(saveBtnText.getPaint().measureText(getString(R.string.save)), saveBtnText.getPaint().measureText(getString(R.string.saved)))+buttonExtraWidth;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
@ -253,7 +268,7 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
|
||||
@Override
|
||||
public void dismiss(){
|
||||
dismissWithAnimation(super::dismiss);
|
||||
dismissWithAnimation(super::dismiss, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -404,13 +419,13 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
}
|
||||
}
|
||||
|
||||
private void dismissWithAnimation(Runnable onDone){
|
||||
private void dismissWithAnimation(Runnable onDone, boolean animateTranslationDown){
|
||||
if(currentTransition!=null)
|
||||
currentTransition.cancel();
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(scrim, "alpha", 0),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.TRANSLATION_Y, V.dp(50)),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.TRANSLATION_Y, particleAnimContainer.getTranslationY()+V.dp(animateTranslationDown ? 50 : -50)),
|
||||
ObjectAnimator.ofFloat(particleAnimContainer, View.ALPHA, 0),
|
||||
ObjectAnimator.ofFloat(getToolbar(), View.ALPHA, 0)
|
||||
);
|
||||
|
@ -449,13 +464,25 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
String fileName=account.username+"_"+accountDomain+".png";
|
||||
try(OutputStream os=destinationStreamForFile(fileName)){
|
||||
bmp.compress(Bitmap.CompressFormat.PNG, 100, os);
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.file_saved, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
saveBtn.setEnabled(false);
|
||||
saveBtnText.setText(R.string.saved);
|
||||
saveBtnText.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check_20px, 0, 0, 0);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.image_saved)
|
||||
.setAction(R.string.view_file, ()->startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
});
|
||||
if(Build.VERSION.SDK_INT<29){
|
||||
File dstFile=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
|
||||
MediaScannerConnection.scanFile(activity, new String[]{dstFile.getAbsolutePath()}, new String[]{"image/png"}, null);
|
||||
}
|
||||
}catch(IOException x){
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -581,4 +608,73 @@ public class ProfileQrCodeFragment extends AppKitFragment{
|
|||
updateParticleEmitter();
|
||||
}
|
||||
}
|
||||
|
||||
private class TouchDismissListener implements View.OnTouchListener{
|
||||
private Rect tmpRect=new Rect();
|
||||
private int[] xy={0, 0};
|
||||
private boolean dragging;
|
||||
private float dragDownY;
|
||||
private VelocityTracker velocityTracker;
|
||||
private SpringAnimation springBackAnim;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev){
|
||||
if(ev.getAction()==MotionEvent.ACTION_DOWN){
|
||||
codeContainer.getLocationInWindow(xy);
|
||||
tmpRect.set(xy[0], xy[1], xy[0]+codeContainer.getWidth(), xy[1]+codeContainer.getHeight());
|
||||
if(springBackAnim!=null){
|
||||
springBackAnim.skipToEnd();
|
||||
}
|
||||
if(tmpRect.contains((int)ev.getX(), (int)ev.getY())){
|
||||
dragging=true;
|
||||
dragDownY=ev.getY();
|
||||
velocityTracker=VelocityTracker.obtain();
|
||||
velocityTracker.addMovement(ev);
|
||||
}else{
|
||||
dismiss();
|
||||
}
|
||||
}else if(dragging){
|
||||
if(ev.getAction()==MotionEvent.ACTION_MOVE){
|
||||
float transY=ev.getY()-dragDownY;
|
||||
particleAnimContainer.setTranslationY(transY);
|
||||
float alpha=1f-Math.abs(transY)/particleAnimContainer.getHeight();
|
||||
scrim.setAlpha(Math.round(alpha*255));
|
||||
getToolbar().setAlpha(alpha);
|
||||
velocityTracker.addMovement(ev);
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_UP){
|
||||
dragging=false;
|
||||
velocityTracker.addMovement(ev);
|
||||
velocityTracker.computeCurrentVelocity(1000);
|
||||
float velocity=velocityTracker.getYVelocity();
|
||||
if(Math.abs(velocity)>=V.dp(1000) || Math.abs(particleAnimContainer.getTranslationY())>particleAnimContainer.getHeight()/4f){
|
||||
dismissWithAnimation(ProfileQrCodeFragment.super::dismiss, velocity>0);
|
||||
}else{
|
||||
springBack(velocity);
|
||||
}
|
||||
velocityTracker.recycle();
|
||||
velocityTracker=null;
|
||||
}else if(ev.getAction()==MotionEvent.ACTION_CANCEL){
|
||||
dragging=false;
|
||||
springBack(velocityTracker.getYVelocity());
|
||||
velocityTracker.recycle();
|
||||
velocityTracker=null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void springBack(float velocityY){
|
||||
SpringAnimation anim=new SpringAnimation(particleAnimContainer, DynamicAnimation.TRANSLATION_Y, 0);
|
||||
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW).setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||
anim.setStartVelocity(velocityY);
|
||||
anim.addEndListener((animation, canceled, value, velocity)->springBackAnim=null);
|
||||
anim.addUpdateListener((animation, value, velocity)->{
|
||||
float alpha=1f-Math.abs(particleAnimContainer.getTranslationY())/particleAnimContainer.getHeight();
|
||||
scrim.setAlpha(Math.round(alpha*255));
|
||||
getToolbar().setAlpha(alpha);
|
||||
});
|
||||
springBackAnim=anim;
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,12 @@ import android.animation.ObjectAnimator;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Insets;
|
||||
|
@ -59,6 +62,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
|||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -122,6 +126,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
private Runnable videoPositionUpdater=this::updateVideoPosition;
|
||||
private int videoDuration, videoInitialPosition, videoLastTimeUpdatePosition;
|
||||
private long videoInitialPositionTime;
|
||||
private long lastDownloadID;
|
||||
private boolean receiverRegistered;
|
||||
|
||||
private static final Property<FragmentRootLinearLayout, Integer> STATUS_BAR_COLOR_PROPERTY=new Property<>(Integer.class, "Fdsafdsa"){
|
||||
@Override
|
||||
|
@ -135,6 +141,21 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver downloadCompletedReceiver=new BroadcastReceiver(){
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
long id=intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
|
||||
if(id==lastDownloadID){
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.video_saved)
|
||||
.setAction(R.string.view_file, ()->activity.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
activity.unregisterReceiver(this);
|
||||
receiverRegistered=false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PhotoViewer(Activity activity, List<Attachment> attachments, int index, Status status, String accountID, Listener listener){
|
||||
this.activity=activity;
|
||||
this.attachments=attachments.stream().filter(a->a.type==Attachment.Type.IMAGE || a.type==Attachment.Type.GIFV || a.type==Attachment.Type.VIDEO).collect(Collectors.toList());
|
||||
|
@ -370,6 +391,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
listener.setPhotoViewVisibility(pager.getCurrentItem(), true);
|
||||
wm.removeView(windowView);
|
||||
listener.photoViewerDismissed();
|
||||
if(receiverRegistered){
|
||||
activity.unregisterReceiver(downloadCompletedReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -531,7 +555,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
BufferedSink buf=Okio.buffer(sink);
|
||||
buf.writeAll(src);
|
||||
buf.flush();
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.file_saved, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.image_saved)
|
||||
.setAction(R.string.view_file, ()->activity.startActivity(new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)))
|
||||
.show();
|
||||
});
|
||||
if(Build.VERSION.SDK_INT<29){
|
||||
String fileName=Uri.parse(att.url).getLastPathSegment();
|
||||
File dstFile=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
|
||||
|
@ -539,12 +568,18 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "doSaveCurrentFile: ", x);
|
||||
activity.runOnUiThread(()->Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show());
|
||||
activity.runOnUiThread(()->{
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
});
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "doSaveCurrentFile: ", x);
|
||||
Toast.makeText(activity, R.string.error_saving_file, Toast.LENGTH_SHORT).show();
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.error_saving_file)
|
||||
.show();
|
||||
}
|
||||
}else{
|
||||
saveViaDownloadManager(att);
|
||||
|
@ -557,8 +592,12 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
|||
req.allowScanningByMediaScanner();
|
||||
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.getLastPathSegment());
|
||||
activity.getSystemService(DownloadManager.class).enqueue(req);
|
||||
Toast.makeText(activity, R.string.downloading, Toast.LENGTH_SHORT).show();
|
||||
activity.registerReceiver(downloadCompletedReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
receiverRegistered=true;
|
||||
lastDownloadID=activity.getSystemService(DownloadManager.class).enqueue(req);
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(R.string.downloading)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onAudioFocusChanged(int change){
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -108,23 +109,38 @@
|
|||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="?colorM3OnPrimary"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:text="@string/share_user"/>
|
||||
</FrameLayout>
|
||||
|
||||
<Button
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/save_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:drawableStart="@drawable/ic_download_20px"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="?colorM3OnPrimary"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:text="@string/save"/>
|
||||
|
||||
android:paddingStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled">
|
||||
<TextView
|
||||
android:id="@+id/save_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:drawableStart="@drawable/ic_download_20px"
|
||||
style="@style/Widget.Mastodon.M3.Button.Filled"
|
||||
android:background="@null"
|
||||
android:padding="0dp"
|
||||
android:drawablePadding="7dp"
|
||||
android:drawableTint="@color/button_text_m3_filled"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:minWidth="0dp"
|
||||
android:duplicateParentState="true"
|
||||
android:text="@string/save"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</view>
|
||||
|
|
|
@ -704,4 +704,9 @@
|
|||
<string name="handle_copied">Handle copied to clipboard.</string>
|
||||
<string name="qr_code">QR code</string>
|
||||
<string name="scan_qr_code">Scan QR code</string>
|
||||
<!-- Shown on a button that saves a file, after it was successfully saved -->
|
||||
<string name="saved">Saved</string>
|
||||
<string name="image_saved">Image saved.</string>
|
||||
<string name="video_saved">Video saved.</string>
|
||||
<string name="view_file">View</string>
|
||||
</resources>
|
Loading…
Reference in New Issue