integrate custom image viewer, closes #36
This commit is contained in:
@@ -34,6 +34,7 @@ using Android.Content.PM;
|
||||
using Android.Webkit;
|
||||
using Android.Graphics;
|
||||
using Java.IO;
|
||||
using keepass2android.EntryActivityClasses;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Security;
|
||||
using KeePassLib.Utility;
|
||||
@@ -623,6 +624,7 @@ namespace keepass2android
|
||||
var itemList = RegisterPopup(popupKey, valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots));
|
||||
itemList.Add(new WriteBinaryToFilePopupItem(key, this));
|
||||
itemList.Add(new OpenBinaryPopupItem(key, this));
|
||||
itemList.Add(new ViewImagePopupItem(key, this));
|
||||
|
||||
|
||||
|
||||
@@ -1115,5 +1117,19 @@ namespace keepass2android
|
||||
},
|
||||
null, timeToWait, TimeSpan.FromMilliseconds(-1));
|
||||
}
|
||||
|
||||
public void ShowAttachedImage(string key)
|
||||
{
|
||||
ProtectedBinary pb = Entry.Binaries.Get(key);
|
||||
System.Diagnostics.Debug.Assert(pb != null);
|
||||
if (pb == null)
|
||||
throw new ArgumentException();
|
||||
byte[] pbData = pb.ReadData();
|
||||
|
||||
Intent imageViewerIntent = new Intent(this, typeof(ImageViewActivity));
|
||||
imageViewerIntent.PutExtra("EntryId", Entry.Uuid.ToHexString());
|
||||
imageViewerIntent.PutExtra("EntryKey", key);
|
||||
StartActivity(imageViewerIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.Res;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
|
||||
namespace keepass2android.EntryActivityClasses
|
||||
{
|
||||
internal class ViewImagePopupItem:IPopupMenuItem
|
||||
{
|
||||
private readonly string _key;
|
||||
private readonly EntryActivity _entryActivity;
|
||||
|
||||
public ViewImagePopupItem(string key, EntryActivity entryActivity)
|
||||
{
|
||||
_key = key;
|
||||
_entryActivity = entryActivity;
|
||||
}
|
||||
public Drawable Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return _entryActivity.Resources.GetDrawable(Resource.Drawable.ic_picture);
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return _entryActivity.Resources.GetString(Resource.String.ShowAttachedImage);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleClick()
|
||||
{
|
||||
_entryActivity.ShowAttachedImage(_key);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
330
src/keepass2android/ImageViewActivity.cs
Normal file
330
src/keepass2android/ImageViewActivity.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Graphics;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Util;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Utility;
|
||||
using Object = Java.Lang.Object;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
|
||||
public class ZoomableImageView : ImageView
|
||||
{
|
||||
|
||||
private class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener
|
||||
{
|
||||
|
||||
private ZoomableImageView parent;
|
||||
|
||||
public ScaleListener(ZoomableImageView parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public override bool OnScaleBegin(ScaleGestureDetector detector)
|
||||
{
|
||||
parent.mode = ZOOM;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool OnScale(ScaleGestureDetector detector)
|
||||
{
|
||||
float scaleFactor = detector.ScaleFactor;
|
||||
float newScale = parent.saveScale * scaleFactor;
|
||||
if (newScale < parent.maxScale && newScale > parent.minScale)
|
||||
{
|
||||
parent.saveScale = newScale;
|
||||
float width = parent.Width;
|
||||
float height = parent.Height;
|
||||
parent.right = (parent.originalBitmapWidth * parent.saveScale) - width;
|
||||
parent.bottom = (parent.originalBitmapHeight * parent.saveScale) - height;
|
||||
|
||||
float scaledBitmapWidth = parent.originalBitmapWidth * parent.saveScale;
|
||||
float scaledBitmapHeight = parent.originalBitmapHeight * parent.saveScale;
|
||||
|
||||
if (scaledBitmapWidth <= width || scaledBitmapHeight <= height)
|
||||
{
|
||||
parent.matrix.PostScale(scaleFactor, scaleFactor, width / 2, height / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.matrix.PostScale(scaleFactor, scaleFactor, detector.FocusX, detector.FocusY);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const int NONE = 0;
|
||||
const int DRAG = 1;
|
||||
const int ZOOM = 2;
|
||||
const int CLICK = 3;
|
||||
|
||||
private int mode = NONE;
|
||||
|
||||
private Matrix matrix = new Matrix();
|
||||
|
||||
private PointF last = new PointF();
|
||||
private PointF start = new PointF();
|
||||
private float minScale = 1.0f;
|
||||
private float maxScale = 100.0f;
|
||||
private float[] m;
|
||||
|
||||
private float redundantXSpace, redundantYSpace;
|
||||
private float saveScale = 1f;
|
||||
private float right, bottom, originalBitmapWidth, originalBitmapHeight;
|
||||
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
protected ZoomableImageView(IntPtr javaReference, JniHandleOwnership transfer)
|
||||
: base(javaReference, transfer)
|
||||
{
|
||||
}
|
||||
public ZoomableImageView(Context context)
|
||||
: base(context)
|
||||
{
|
||||
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableImageView(Context context, IAttributeSet attrs)
|
||||
: base(context, attrs)
|
||||
{
|
||||
init(context);
|
||||
}
|
||||
|
||||
public ZoomableImageView(Context context, IAttributeSet attrs, int defStyleAttr)
|
||||
: base(context, attrs, defStyleAttr)
|
||||
{
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context)
|
||||
{
|
||||
base.Clickable = true;
|
||||
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener(this));
|
||||
m = new float[9];
|
||||
ImageMatrix = matrix;
|
||||
SetScaleType(ScaleType.Matrix);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||
{
|
||||
base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int bmHeight = getBmHeight();
|
||||
int bmWidth = getBmWidth();
|
||||
|
||||
float width = MeasuredWidth;
|
||||
float height = MeasuredHeight;
|
||||
//Fit to screen.
|
||||
float scale = width > height ? height / bmHeight : width / bmWidth;
|
||||
|
||||
matrix.SetScale(scale, scale);
|
||||
saveScale = 1f;
|
||||
|
||||
originalBitmapWidth = scale * bmWidth;
|
||||
originalBitmapHeight = scale * bmHeight;
|
||||
|
||||
// Center the image
|
||||
redundantYSpace = (height - originalBitmapHeight);
|
||||
redundantXSpace = (width - originalBitmapWidth);
|
||||
|
||||
matrix.PostTranslate(redundantXSpace / 2, redundantYSpace / 2);
|
||||
|
||||
ImageMatrix = matrix;
|
||||
}
|
||||
|
||||
|
||||
public override bool OnTouchEvent(MotionEvent event_)
|
||||
{
|
||||
mScaleDetector.OnTouchEvent(event_);
|
||||
|
||||
matrix.GetValues(m);
|
||||
float x = m[Matrix.MtransX];
|
||||
float y = m[Matrix.MtransY];
|
||||
PointF curr = new PointF(event_.GetX(), event_.GetY());
|
||||
Log.Debug("TOUCH", event_.Action.ToString(), " mode=" + mode);
|
||||
switch (event_.Action)
|
||||
{
|
||||
//when one finger is touching
|
||||
//set the mode to DRAG
|
||||
case MotionEventActions.Down:
|
||||
last.Set(event_.GetX(), event_.GetY());
|
||||
start.Set(last);
|
||||
mode = DRAG;
|
||||
break;
|
||||
//when two fingers are touching
|
||||
//set the mode to ZOOM
|
||||
case MotionEventActions.Pointer2Down:
|
||||
case MotionEventActions.PointerDown:
|
||||
last.Set(event_.GetX(), event_.GetY());
|
||||
start.Set(last);
|
||||
mode = ZOOM;
|
||||
break;
|
||||
//when a finger moves
|
||||
//If mode is applicable move image
|
||||
case MotionEventActions.Move:
|
||||
//if the mode is ZOOM or
|
||||
//if the mode is DRAG and already zoomed
|
||||
if (mode == ZOOM || (mode == DRAG && saveScale > minScale))
|
||||
{
|
||||
float deltaX = curr.X - last.X;// x difference
|
||||
float deltaY = curr.Y - last.Y;// y difference
|
||||
float scaleWidth = (float)System.Math.Round(originalBitmapWidth * saveScale);// width after applying current scale
|
||||
float scaleHeight = (float)System.Math.Round(originalBitmapHeight * saveScale);// height after applying current scale
|
||||
|
||||
bool limitX = false;
|
||||
bool limitY = false;
|
||||
|
||||
//if scaleWidth is smaller than the views width
|
||||
//in other words if the image width fits in the view
|
||||
//limit left and right movement
|
||||
if (scaleWidth < Width && scaleHeight < Height)
|
||||
{
|
||||
// don't do anything
|
||||
}
|
||||
else if (scaleWidth < Width)
|
||||
{
|
||||
deltaX = 0;
|
||||
limitY = true;
|
||||
}
|
||||
//if scaleHeight is smaller than the views height
|
||||
//in other words if the image height fits in the view
|
||||
//limit up and down movement
|
||||
else if (scaleHeight < Height)
|
||||
{
|
||||
deltaY = 0;
|
||||
limitX = true;
|
||||
}
|
||||
//if the image doesnt fit in the width or height
|
||||
//limit both up and down and left and right
|
||||
else
|
||||
{
|
||||
limitX = true;
|
||||
limitY = true;
|
||||
}
|
||||
|
||||
if (limitY)
|
||||
{
|
||||
if (y + deltaY > 0)
|
||||
{
|
||||
deltaY = -y;
|
||||
}
|
||||
else if (y + deltaY < -bottom)
|
||||
{
|
||||
deltaY = -(y + bottom);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (limitX)
|
||||
{
|
||||
if (x + deltaX > 0)
|
||||
{
|
||||
deltaX = -x;
|
||||
}
|
||||
else if (x + deltaX < -right)
|
||||
{
|
||||
deltaX = -(x + right);
|
||||
}
|
||||
|
||||
}
|
||||
//move the image with the matrix
|
||||
matrix.PostTranslate(deltaX, deltaY);
|
||||
//set the last touch location to the current
|
||||
last.Set(curr.X, curr.Y);
|
||||
}
|
||||
break;
|
||||
//first finger is lifted
|
||||
case MotionEventActions.Up:
|
||||
mode = NONE;
|
||||
int xDiff = (int)System.Math.Abs(curr.X - start.X);
|
||||
int yDiff = (int)System.Math.Abs(curr.Y - start.Y);
|
||||
if (xDiff < CLICK && yDiff < CLICK)
|
||||
PerformClick();
|
||||
break;
|
||||
// second finger is lifted
|
||||
case MotionEventActions.Pointer2Up:
|
||||
case MotionEventActions.PointerUp:
|
||||
mode = NONE;
|
||||
break;
|
||||
}
|
||||
ImageMatrix = matrix;
|
||||
Invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setMaxZoom(float x)
|
||||
{
|
||||
maxScale = x;
|
||||
}
|
||||
|
||||
private int getBmWidth()
|
||||
{
|
||||
Drawable drawable = Drawable;
|
||||
if (drawable != null)
|
||||
{
|
||||
return drawable.IntrinsicWidth;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getBmHeight()
|
||||
{
|
||||
Drawable drawable = Drawable;
|
||||
if (drawable != null)
|
||||
{
|
||||
return drawable.IntrinsicHeight;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
[Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden,
|
||||
Theme = "@style/MyTheme_ActionBar")]
|
||||
public class ImageViewActivity : LockCloseActivity
|
||||
{
|
||||
private ActivityDesign _activityDesign;
|
||||
|
||||
public ImageViewActivity()
|
||||
{
|
||||
_activityDesign = new ActivityDesign(this);
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
_activityDesign.ReapplyTheme();
|
||||
}
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
_activityDesign.ApplyTheme();
|
||||
base.OnCreate(savedInstanceState);
|
||||
SetContentView(Resource.Layout.ImageViewActivity);
|
||||
var uuid = new PwUuid(MemUtil.HexStringToByteArray(Intent.GetStringExtra("EntryId")));
|
||||
string key = Intent.GetStringExtra("EntryKey");
|
||||
var binary = App.Kp2a.GetDb().Entries[uuid].Binaries.Get(key);
|
||||
SupportActionBar.Title = key;
|
||||
byte[] pbdata = binary.ReadData();
|
||||
|
||||
var bmp = BitmapFactory.DecodeByteArray(pbdata,0,pbdata.Length);
|
||||
|
||||
FindViewById<ImageView>(Resource.Id.imageView).SetImageBitmap(bmp);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/keepass2android/Resources/drawable-mdpi/ic_picture.png
Normal file
BIN
src/keepass2android/Resources/drawable-mdpi/ic_picture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 385 B |
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_picture.png
Normal file
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_picture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 767 B |
10
src/keepass2android/Resources/layout/ImageViewActivity.xml
Normal file
10
src/keepass2android/Resources/layout/ImageViewActivity.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<keepass2android.ZoomableImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
@@ -6,7 +6,7 @@
|
||||
<string name="AboutText">Keepass2Android is a password manager providing read/write access to KeePass 2.x databases on Android.</string>
|
||||
<string name="CreditsText">The User Interface is based on a port of KeepassDroid developed by Brian Pellin. Code for database operations is based on KeePass by Dominik Reichl. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.</string>
|
||||
<string name="CreditsTextSFTP">SFTP support is implemented using the JSch library under BSD licence, created by JCraft, Inc.</string>
|
||||
<string name="CreditsIcons">The Hammer Icon is Created by John Caserta from the Noun Project. The Penguin Icon is Created by Adriano Emerick from the Noun Project. The Feather icon is Created by Jon Testa from the Noun Project. The Apple icon is Created by Ava Rowell from the Noun Project. </string>
|
||||
<string name="CreditsIcons">The Hammer Icon is Created by John Caserta from the Noun Project. The Penguin Icon is Created by Adriano Emerick from the Noun Project. The Feather icon is Created by Jon Testa from the Noun Project. The Apple icon is Created by Ava Rowell from the Noun Project. The Picture icon is from https://icons8.com/icon/5570/Picture.</string>
|
||||
<string name="accept">Accept</string>
|
||||
<string name="deny">Deny</string>
|
||||
<string name="add_entry">Add entry</string>
|
||||
@@ -310,7 +310,9 @@
|
||||
<string name="SaveAttachmentDialog_text">Please select where to save the attachment.</string>
|
||||
<string name="SaveAttachmentDialog_save">Save to SD card</string>
|
||||
<string name="SaveAttachmentDialog_open">Save to cache and open</string>
|
||||
<string name="ShowAttachedImage">Show with internal image viewer</string>
|
||||
|
||||
|
||||
<string name="SaveAttachment_doneMessage">Saved file to %1$s.</string>
|
||||
<string name="SaveAttachment_Failed">Could not save attachment to %1$s.</string>
|
||||
|
||||
|
||||
@@ -128,6 +128,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ActivateAutoFillActivity.cs" />
|
||||
<Compile Include="EntryActivityClasses\ViewImagePopupItem.cs" />
|
||||
<Compile Include="ImageViewActivity.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpAuxCachingFileStorage.cs" />
|
||||
@@ -1708,6 +1710,15 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\layout\owncloudcredentials.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\layout\ImageViewActivity.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-mdpi\ic_picture.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_picture.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
<Import Project="..\packages\Xamarin.Insights.1.11.3\build\MonoAndroid10\Xamarin.Insights.targets" Condition="Exists('..\packages\Xamarin.Insights.1.11.3\build\MonoAndroid10\Xamarin.Insights.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
|
||||
Reference in New Issue
Block a user