diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java index 7244fcf33..46a3bd7c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/transfer/view/TransferFragment.java @@ -56,6 +56,7 @@ import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter; import org.sufficientlysecure.keychain.ui.transfer.presenter.TransferPresenter.TransferMvpView; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.widget.ConnectionStatusView; @RequiresApi(api = VERSION_CODES.LOLLIPOP) @@ -74,6 +75,8 @@ public class TransferFragment extends Fragment implements TransferMvpView { private ViewAnimator vTransferAnimator; private TextView vConnectionStatusText1; private TextView vConnectionStatusText2; + private ConnectionStatusView vConnectionStatusView1; + private ConnectionStatusView vConnectionStatusView2; private RecyclerView vTransferKeyList; private View vTransferKeyListEmptyView; private RecyclerView vReceivedKeyList; @@ -100,6 +103,8 @@ public class TransferFragment extends Fragment implements TransferMvpView { vConnectionStatusText1 = (TextView) view.findViewById(R.id.connection_status_1); vConnectionStatusText2 = (TextView) view.findViewById(R.id.connection_status_2); + vConnectionStatusView1 = (ConnectionStatusView) view.findViewById(R.id.connection_status_icon_1); + vConnectionStatusView2 = (ConnectionStatusView) view.findViewById(R.id.connection_status_icon_2); vTransferKeyList = (RecyclerView) view.findViewById(R.id.transfer_key_list); vTransferKeyListEmptyView = view.findViewById(R.id.transfer_key_list_empty); vReceivedKeyList = (RecyclerView) view.findViewById(R.id.received_key_list); @@ -163,8 +168,13 @@ public class TransferFragment extends Fragment implements TransferMvpView { @Override public void showConnectionEstablished(String hostname) { String statusText = getString(R.string.transfer_status_connected, hostname); + vConnectionStatusText1.setText(statusText); vConnectionStatusText2.setText(statusText); + + vConnectionStatusView1.setConnected(true); + vConnectionStatusView2.setConnected(true); + vTransferAnimator.setDisplayedChild(VIEW_CONNECTED); } @@ -177,6 +187,9 @@ public class TransferFragment extends Fragment implements TransferMvpView { public void showViewDisconnected() { vConnectionStatusText1.setText(R.string.transfer_status_disconnected); vConnectionStatusText2.setText(R.string.transfer_status_disconnected); + + vConnectionStatusView1.setConnected(false); + vConnectionStatusView2.setConnected(false); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ConnectionStatusView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ConnectionStatusView.java new file mode 100644 index 000000000..9bceea5bf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ConnectionStatusView.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.widget; + + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + + +public class ConnectionStatusView extends View { + private static final int ARC_COUNT = 3; + public static final int COLOR_CONNECTED = 0xff394baf; + public static final int COLOR_DISCONNECTED = 0xffcccccc; + + + private Arc[] arcs; + private ValueAnimator[] animators; + private boolean isConnected = false; + + + public ConnectionStatusView(Context context) { + super(context); + } + + public ConnectionStatusView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ConnectionStatusView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + initializeObjects(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int measuredWidth = resolveSize(150, widthMeasureSpec); + final int measuredHeight = resolveSize(150, heightMeasureSpec); + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + public void onDraw(Canvas canvas) { + for (int i = 0; i < ARC_COUNT; i++) { + Arc arc = arcs[i]; + canvas.drawArc(arc.oval, 225, 90, false, arc.paint); + } + + if (isConnected != isAnimationInitiated()) { + resetAnimations(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + cancelAnimations(); + } + + public void setConnected(boolean isConnected) { + this.isConnected = isConnected; + + if (arcs != null) { + for (int i = 0; i < ARC_COUNT; i++) { + arcs[i].paint.setColor(isConnected ? COLOR_CONNECTED : COLOR_DISCONNECTED); + } + } + + invalidate(); + } + + private void resetAnimations() { + if (isConnected != isAnimationInitiated()) { + post(new Runnable() { + @Override + public void run() { + if (isConnected) { + setupAnimations(); + } else { + cancelAnimations(); + } + } + }); + } + } + + private boolean isAnimationInitiated() { + return animators != null; + } + + private void setupAnimations() { + if (isAnimationInitiated()) { + return; + } + + animators = new ValueAnimator[ARC_COUNT]; + for (int i = 0; i < ARC_COUNT; i++) { + final int index = i; + ValueAnimator animator = ValueAnimator.ofInt(100, 255, 100); + animator.setRepeatCount(ValueAnimator.INFINITE); + animator.setDuration(2000); + animator.setStartDelay(i * 300); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + arcs[index].paint.setAlpha((int) animation.getAnimatedValue()); + invalidate(); + } + }); + animator.start(); + + animators[i] = animator; + } + } + + private void cancelAnimations() { + if (!isAnimationInitiated()) { + return; + } + + for (int i = 0; i < ARC_COUNT; i++) { + animators[i].cancel(); + } + animators = null; + } + + private void initializeObjects() { + int width = getWidth(); + int height = getHeight(); + float centerX = width / 2.0f; + float centerY = height / 2.0f; + float r = Math.min(width, height) / 2f; + + arcs = new Arc[ARC_COUNT]; + for (int i = 0; i < ARC_COUNT; i++) { + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(r / 10f); + paint.setColor(isConnected ? COLOR_CONNECTED : COLOR_DISCONNECTED); + + float d = r / 4 + i * r / 4; + RectF oval = new RectF(centerX - d, centerY - d + r / 3, centerX + d, centerY + d + r / 3); + + arcs[i] = new Arc(paint, oval); + } + } + + private static class Arc { + private final Paint paint; + private final RectF oval; + + Arc(Paint paint, RectF oval) { + this.paint = paint; + this.oval = oval; + } + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/transfer_fragment.xml b/OpenKeychain/src/main/res/layout/transfer_fragment.xml index 870b0c634..cde70eef5 100644 --- a/OpenKeychain/src/main/res/layout/transfer_fragment.xml +++ b/OpenKeychain/src/main/res/layout/transfer_fragment.xml @@ -8,7 +8,7 @@ android:id="@+id/transfer_animator" android:inAnimation="@anim/fade_in_delayed" android:outAnimation="@anim/fade_out" - custom:initialView="0"> + custom:initialView="01"> - + android:layout_height="wrap_content"> - + + + + + + + - + android:layout_height="wrap_content"> - + + + + + + +