diff options
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/Constants.java | 1 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/EipFragment.java | 41 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/eip/EIP.java | 7 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java | 61 | ||||
-rw-r--r-- | app/src/main/res/drawable/progressbar_circle.xml | 83 | ||||
-rw-r--r-- | app/src/main/res/layout-xlarge/eip_service_fragment.xml | 33 | ||||
-rw-r--r-- | app/src/main/res/layout/eip_service_fragment.xml | 45 | ||||
-rw-r--r-- | app/src/main/res/layout/v_main_button.xml | 132 | ||||
-rw-r--r-- | app/src/main/res/values/dimens.xml | 1 |
9 files changed, 321 insertions, 83 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index acfddf5d..f197c2ed 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -45,6 +45,7 @@ public interface Constants { String EIP_REQUEST = "EIP.REQUEST"; String EIP_RESTART_ON_BOOT = "EIP.RESTART_ON_BOOT"; String EIP_IS_ALWAYS_ON = "EIP.EIP_IS_ALWAYS_ON"; + String EIP_TRIGGERED_FROM_UI = "EIP.TRIGGERED_FROM_UI"; ////////////////////////////////////////////// diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index 844bfd7d..3c541e71 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -53,6 +53,7 @@ import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VoidVpnService; +import se.leap.bitmaskclient.views.VpnStateImage; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -66,6 +67,7 @@ import static se.leap.bitmaskclient.Constants.EIP_NOTIFICATION; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.Constants.EIP_TRIGGERED_FROM_UI; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOWED_REGISTERED; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; @@ -85,11 +87,8 @@ public class EipFragment extends Fragment implements Observer { @InjectView(R.id.background) AppCompatImageView background; - @InjectView(R.id.key) - AppCompatImageView key; - - @InjectView(R.id.cirle) - AppCompatImageView circle; + @InjectView(R.id.vpn_state_image) + VpnStateImage vpnStateImage; @InjectView(R.id.vpn_main_button) Button mainButton; @@ -186,13 +185,8 @@ public class EipFragment extends Fragment implements Observer { handleIcon(); } - @OnClick(R.id.key) - void onKeyClick() { - handleIcon(); - } - - @OnClick(R.id.cirle) - void onCircleClick() { + @OnClick(R.id.vpn_state_image) + void onVpnStateImageClick() { handleIcon(); } @@ -262,6 +256,10 @@ public class EipFragment extends Fragment implements Observer { wantsToConnect = false; saveStatus(true); eipCommand(EIP_ACTION_START); + vpnStateImage.showProgress(); + routedText.setVisibility(GONE); + vpnRoute.setVisibility(GONE); + colorBackgroundALittle(); } private void stop() { @@ -333,9 +331,13 @@ public class EipFragment extends Fragment implements Observer { */ private void eipCommand(String action) { Activity activity = getActivity(); + if (activity == null) { + return; + } // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? Intent vpn_intent = new Intent(activity.getApplicationContext(), EIP.class); vpn_intent.setAction(action); + vpn_intent.putExtra(EIP_TRIGGERED_FROM_UI, true); vpn_intent.putExtra(EIP_RECEIVER, eipReceiver); activity.startService(vpn_intent); } @@ -360,22 +362,29 @@ public class EipFragment extends Fragment implements Observer { private void handleNewState() { Activity activity = getActivity(); + if (activity == null) { + return; + } + if (eipStatus.isConnecting()) { mainButton.setText(activity.getString(android.R.string.cancel)); - key.setImageResource(R.drawable.vpn_connecting); + vpnStateImage.setStateIcon(R.drawable.vpn_connecting); + vpnStateImage.showProgress(); routedText.setVisibility(GONE); vpnRoute.setVisibility(GONE); colorBackgroundALittle(); } else if (eipStatus.isConnected() || isOpenVpnRunningWithoutNetwork()) { mainButton.setText(activity.getString(R.string.vpn_button_turn_off)); - key.setImageResource(R.drawable.vpn_connected); + vpnStateImage.setStateIcon(R.drawable.vpn_connected); + vpnStateImage.stopProgress(); routedText.setVisibility(VISIBLE); vpnRoute.setVisibility(VISIBLE); vpnRoute.setText(ConfigHelper.getProviderName(preferences)); colorBackground(); } else { mainButton.setText(activity.getString(R.string.vpn_button_turn_on)); - key.setImageResource(R.drawable.vpn_disconnected); + vpnStateImage.setStateIcon(R.drawable.vpn_disconnected); + vpnStateImage.stopProgress(); routedText.setVisibility(GONE); vpnRoute.setVisibility(GONE); greyscaleBackground(); @@ -482,7 +491,7 @@ public class EipFragment extends Fragment implements Observer { private void colorBackground() { background.setColorFilter(null); - background.setImageAlpha(255); + background.setImageAlpha(210); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index b8858c1e..894ad672 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -41,6 +41,7 @@ import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.Constants.EIP_TRIGGERED_FROM_UI; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; @@ -93,7 +94,7 @@ public final class EIP extends IntentService { switch (action) { case EIP_ACTION_START: - startEIP(); + startEIP(!intent.hasExtra(EIP_TRIGGERED_FROM_UI)); break; case EIP_ACTION_START_ALWAYS_ON_VPN: startEIPAlwaysOnVpn(); @@ -118,13 +119,13 @@ public final class EIP extends IntentService { * Intent to {@link de.blinkt.openvpn.LaunchVPN}. * It also sets up early routes. */ - private void startEIP() { + private void startEIP(boolean earlyRoutes) { if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)){ preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } if (gatewaysManager.isEmpty()) updateEIPService(); - if (!EipStatus.getInstance().isBlockingVpnEstablished()) { + if (!EipStatus.getInstance().isBlockingVpnEstablished() && earlyRoutes) { earlyRoutes(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java b/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java new file mode 100644 index 00000000..46221ae8 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java @@ -0,0 +1,61 @@ +package se.leap.bitmaskclient.views; + +import android.content.Context; +import android.support.constraint.ConstraintLayout; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ProgressBar; + +import se.leap.bitmaskclient.R; + +/** + * Created by cyberta on 12.02.18. + */ + + +public class VpnStateImage extends ConstraintLayout { + + ProgressBar progressBar; + AppCompatImageView stateIcon; + + public VpnStateImage(Context context) { + super(context); + initLayout(context); + } + + public VpnStateImage(Context context, AttributeSet attrs) { + super(context, attrs); + initLayout(context); + } + + public VpnStateImage(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context); + } + + void initLayout(Context context) { + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View rootview = inflater.inflate(R.layout.v_main_button, this, true); + stateIcon = rootview.findViewById(R.id.vpn_state_key); + progressBar = rootview.findViewById(R.id.progressBar); + progressBar.setIndeterminate(true); + } + + public void showProgress() { + progressBar.setVisibility(VISIBLE); + } + + + public void stopProgress() { + progressBar.setVisibility(GONE); + } + + public void setStateIcon(int resource) { + stateIcon.setImageResource(resource); + } + + +} diff --git a/app/src/main/res/drawable/progressbar_circle.xml b/app/src/main/res/drawable/progressbar_circle.xml new file mode 100644 index 00000000..5d28e05b --- /dev/null +++ b/app/src/main/res/drawable/progressbar_circle.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:id="@+id/progress1"> + <rotate + android:fromDegrees="-90" + android:pivotX="50%" + android:pivotY="50%" + android:toDegrees="270"> + + <shape + android:shape="ring" + android:useLevel="true"> + <gradient + android:endColor="#c5e1a5" + android:centerColor="#c5e1a5" + android:startColor="#00ffe082" + android:type="sweep" + /> + </shape> + </rotate> + </item> + + <item android:id="@+id/progress2"> + <rotate + android:fromDegrees="0" + android:pivotX="50%" + android:pivotY="50%" + android:toDegrees="360"> + + <shape + android:shape="ring" + android:useLevel="true"> + <gradient + android:endColor="#ffcc80" + android:centerColor="#ffcc80" + android:startColor="#00ffcc80" + android:type="sweep" + /> + </shape> + </rotate> + </item> + + <item android:id="@+id/progress3"> + <rotate + android:fromDegrees="90" + android:pivotX="50%" + android:pivotY="50%" + android:toDegrees="450"> + <shape + android:shape="ring" + android:useLevel="true"> + <gradient + android:endColor="#ce93d8" + android:centerColor="#ce93d8" + android:startColor="#00ce93d8" + android:type="sweep" + /> + </shape> + </rotate> + </item> + + <item android:id="@+id/progress4"> + <rotate + android:fromDegrees="180" + android:pivotX="50%" + android:pivotY="50%" + android:toDegrees="540"> + <shape + android:shape="ring" + android:useLevel="true"> + <gradient + android:endColor="#81d4fa" + android:centerColor="#81d4fa" + android:startColor="#0081d4fa" + android:type="sweep" + /> + </shape> + </rotate> + </item> + +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml index 497d2c0b..b7af5797 100644 --- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml +++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml @@ -69,41 +69,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - <android.support.v7.widget.AppCompatImageView - android:id="@+id/cirle" + <se.leap.bitmaskclient.views.VpnStateImage + android:id="@+id/vpn_state_image" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginBottom="@dimen/stdpadding" - android:layout_marginEnd="@dimen/stdpadding" - android:layout_marginStart="@dimen/stdpadding" - android:layout_marginTop="@dimen/stdpadding" - android:layout_marginLeft="@dimen/stdpadding" - android:layout_marginRight="@dimen/stdpadding" + android:layout_margin="@dimen/stdpadding" app:layout_constraintBottom_toTopOf="@+id/guideline_horizontal_bottom" app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" - app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" app:layout_constraintTop_toTopOf="@+id/guideline_horizontal_top" - app:layout_constraintVertical_bias="0.0" - app:srcCompat="@drawable/black_circle" /> - - <android.support.v7.widget.AppCompatImageView - android:id="@+id/key" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginBottom="@dimen/stdpadding" - android:layout_marginEnd="@dimen/stdpadding" - android:layout_marginStart="@dimen/stdpadding" - android:layout_marginTop="@dimen/stdpadding" - android:layout_marginLeft="@dimen/stdpadding" - android:layout_marginRight="@dimen/stdpadding" - app:layout_constraintBottom_toBottomOf="@+id/cirle" - app:layout_constraintEnd_toEndOf="@+id/cirle" - app:layout_constraintStart_toStartOf="@+id/cirle" - app:layout_constraintTop_toTopOf="@+id/cirle" - app:srcCompat="@drawable/vpn_connected" /> - + app:layout_constraintDimensionRatio="1:1" /> <android.support.v7.widget.AppCompatButton android:id="@+id/vpn_main_button" diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml index 497d2c0b..814ec310 100644 --- a/app/src/main/res/layout/eip_service_fragment.xml +++ b/app/src/main/res/layout/eip_service_fragment.xml @@ -1,11 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:id="@+id/eipServiceFragment" - > + android:id="@+id/eipServiceFragment"> <android.support.constraint.Guideline android:id="@+id/guideline_horizontal_top" @@ -14,7 +12,7 @@ android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" - app:layout_constraintGuide_percent="0.3" + app:layout_constraintGuide_percent="0.225" /> <android.support.constraint.Guideline @@ -23,7 +21,7 @@ android:layout_height="0dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintGuide_percent="0.3" + app:layout_constraintGuide_percent="0.225" /> @@ -34,7 +32,7 @@ android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" - app:layout_constraintGuide_percent="0.7" + app:layout_constraintGuide_percent="0.775" /> <android.support.constraint.Guideline @@ -43,7 +41,7 @@ android:layout_height="0dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintGuide_percent="0.7" + app:layout_constraintGuide_percent="0.775" /> <android.support.v7.widget.AppCompatImageView @@ -70,40 +68,17 @@ app:layout_constraintTop_toTopOf="parent" /> - <android.support.v7.widget.AppCompatImageView - android:id="@+id/cirle" + <se.leap.bitmaskclient.views.VpnStateImage + android:id="@+id/vpn_state_image" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginBottom="@dimen/stdpadding" - android:layout_marginEnd="@dimen/stdpadding" - android:layout_marginStart="@dimen/stdpadding" - android:layout_marginTop="@dimen/stdpadding" - android:layout_marginLeft="@dimen/stdpadding" - android:layout_marginRight="@dimen/stdpadding" + android:layout_margin="@dimen/stdpadding" app:layout_constraintBottom_toTopOf="@+id/guideline_horizontal_bottom" app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" - app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" app:layout_constraintTop_toTopOf="@+id/guideline_horizontal_top" - app:layout_constraintVertical_bias="0.0" - app:srcCompat="@drawable/black_circle" /> - - <android.support.v7.widget.AppCompatImageView - android:id="@+id/key" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginBottom="@dimen/stdpadding" - android:layout_marginEnd="@dimen/stdpadding" - android:layout_marginStart="@dimen/stdpadding" - android:layout_marginTop="@dimen/stdpadding" - android:layout_marginLeft="@dimen/stdpadding" - android:layout_marginRight="@dimen/stdpadding" - app:layout_constraintBottom_toBottomOf="@+id/cirle" - app:layout_constraintEnd_toEndOf="@+id/cirle" - app:layout_constraintStart_toStartOf="@+id/cirle" - app:layout_constraintTop_toTopOf="@+id/cirle" - app:srcCompat="@drawable/vpn_connected" /> - + app:layout_constraintDimensionRatio="1:1" + /> <android.support.v7.widget.AppCompatButton android:id="@+id/vpn_main_button" diff --git a/app/src/main/res/layout/v_main_button.xml b/app/src/main/res/layout/v_main_button.xml new file mode 100644 index 00000000..ad1ac4ba --- /dev/null +++ b/app/src/main/res/layout/v_main_button.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + <android.support.constraint.Guideline + android:id="@+id/vpn_btn_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.125" /> + + <android.support.constraint.Guideline + android:id="@+id/vpn_btn_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.875" /> + + <android.support.constraint.Guideline + android:id="@+id/vpn_btn_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.125" /> + + <android.support.constraint.Guideline + android:id="@+id/vpn_btn_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.875" /> + + + <android.support.constraint.Guideline + android:id="@+id/icn_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.2" /> + + <android.support.constraint.Guideline + android:id="@+id/icn_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.8" /> + + <android.support.constraint.Guideline + android:id="@+id/icn_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.2" /> + + <android.support.constraint.Guideline + android:id="@+id/icn_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.8" /> + + <android.support.constraint.Guideline + android:id="@+id/border_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.025" /> + + <android.support.constraint.Guideline + android:id="@+id/border_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.975" /> + + <android.support.constraint.Guideline + android:id="@+id/border_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.025" /> + + <android.support.constraint.Guideline + android:id="@+id/border_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.975" /> + + + <ProgressBar + android:id="@+id/progressBar" + style="@style/Widget.AppCompat.ProgressBar.Horizontal" + + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@id/border_guideline_bottom" + app:layout_constraintEnd_toEndOf="@id/border_guideline_right" + app:layout_constraintStart_toStartOf="@id/border_guideline_left" + app:layout_constraintTop_toTopOf="@id/border_guideline_top" + android:indeterminate="true" + android:indeterminateDuration="2000" + android:indeterminateDrawable="@drawable/progressbar_circle" + android:interpolator="@android:anim/accelerate_decelerate_interpolator" + android:indeterminateBehavior="cycle" + /> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/circle" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/vpn_btn_guideline_bottom" + app:layout_constraintEnd_toStartOf="@+id/vpn_btn_guideline_right" + app:layout_constraintStart_toStartOf="@+id/vpn_btn_guideline_left" + app:layout_constraintTop_toTopOf="@+id/vpn_btn_guideline_top" + app:srcCompat="@drawable/black_circle" /> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/vpn_state_key" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@id/icn_guideline_bottom" + app:layout_constraintEnd_toStartOf="@id/icn_guideline_right" + app:layout_constraintStart_toStartOf="@id/icn_guideline_left" + app:layout_constraintTop_toTopOf="@id/icn_guideline_top" + app:srcCompat="@drawable/vpn_connected" /> + +</android.support.constraint.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 87d8e266..f160487b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -7,6 +7,7 @@ <dimen name="paddingItemsSidebarLog">20dp</dimen> <dimen name="stdpadding">8dp</dimen> <dimen name="standard_margin">8dp</dimen> + <dimen name="mainbutton_padding">20dp</dimen> <bool name="logSildersAlwaysVisible">false</bool> <dimen name="diameter">48dp</dimen> |