diff options
author | cyBerta <cyberta@riseup.net> | 2022-11-12 22:21:31 +0100 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2022-11-22 01:48:38 +0100 |
commit | cac2c849e8d7d35bd9c72fc8c9bb8bc322eb24a0 (patch) | |
tree | 398459f10c349268e704439c041874b8639e21f8 /app | |
parent | 7d5e33aeab1ba02148be041c29ad6b4220d83b27 (diff) |
create UI for message of the day
Diffstat (limited to 'app')
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java | 84 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java | 18 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/fragments/MotdFragment.java | 103 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java | 1 | ||||
-rw-r--r-- | app/src/main/res/drawable-hdpi/ic_arrow_right.png | bin | 0 -> 562 bytes | |||
-rw-r--r-- | app/src/main/res/drawable-ldpi/ic_arrow_right.png | bin | 0 -> 458 bytes | |||
-rw-r--r-- | app/src/main/res/drawable-mdpi/ic_arrow_right.png | bin | 0 -> 551 bytes | |||
-rw-r--r-- | app/src/main/res/drawable-xhdpi/ic_arrow_right.png | bin | 0 -> 753 bytes | |||
-rw-r--r-- | app/src/main/res/drawable-xxhdpi/ic_arrow_right.png | bin | 0 -> 784 bytes | |||
-rw-r--r-- | app/src/main/res/drawable-xxxhdpi/ic_arrow_right.png | bin | 0 -> 1168 bytes | |||
-rw-r--r-- | app/src/main/res/layout/f_motd.xml | 116 |
11 files changed, 290 insertions, 32 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 8cb12652..e2fa0783 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -17,6 +17,34 @@ package se.leap.bitmaskclient.base; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; +import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; +import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; +import static se.leap.bitmaskclient.base.models.Constants.EXTRA_MOTD_MSG; +import static se.leap.bitmaskclient.base.models.Constants.LOCATION; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP; +import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; +import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; +import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; + import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -40,6 +68,7 @@ import se.leap.bitmaskclient.base.fragments.EipFragment; import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment; import se.leap.bitmaskclient.base.fragments.LogFragment; import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.base.fragments.MotdFragment; import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; import se.leap.bitmaskclient.base.fragments.SettingsFragment; import se.leap.bitmaskclient.base.models.Provider; @@ -49,38 +78,10 @@ import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.eip.EipSetupListener; import se.leap.bitmaskclient.eip.EipSetupObserver; -import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.providersetup.ProviderAPI; import se.leap.bitmaskclient.providersetup.activities.LoginActivity; import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; -import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; -import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; -import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; -import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; -import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; -import static se.leap.bitmaskclient.base.models.Constants.LOCATION; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; -import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP; -import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; -import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; -import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; - public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer { @@ -93,6 +94,7 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; public final static String ACTION_SHOW_LOG_FRAGMENT = "action_show_log_fragment"; public final static String ACTION_SHOW_DIALOG_FRAGMENT = "action_show_dialog_fragment"; + public final static String ACTION_SHOW_MOTD_FRAGMENT = "action_show_motd_fragment"; /** * Fragment managing the behaviors, interactions and presentation of the navigation drawer. @@ -161,10 +163,22 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, bundle.putParcelable(PROVIDER_KEY, provider); fragment.setArguments(bundle); hideActionBarSubTitle(); + showActionBar(); + break; + case ACTION_SHOW_MOTD_FRAGMENT: + fragment = new MotdFragment(); + Bundle motdBundle = new Bundle(); + if (intent.hasExtra(EXTRA_MOTD_MSG)) { + motdBundle.putString(EXTRA_MOTD_MSG, intent.getStringExtra(EXTRA_MOTD_MSG)); + } + fragment.setArguments(motdBundle); + hideActionBarSubTitle(); + hideActionBar(); break; case ACTION_SHOW_LOG_FRAGMENT: fragment = new LogFragment(); setActionBarTitle(R.string.log_fragment_title); + showActionBar(); break; case ACTION_SHOW_DIALOG_FRAGMENT: if (intent.hasExtra(EIP.ERRORID)) { @@ -186,6 +200,20 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, } } + private void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + private void showActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + private void hideActionBarSubTitle() { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java index 537c6c91..6e557236 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java @@ -16,9 +16,11 @@ */ package se.leap.bitmaskclient.base; +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_MOTD_FRAGMENT; import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.base.models.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.base.models.Constants.EXTRA_MOTD_MSG; import static se.leap.bitmaskclient.base.models.Constants.PREFERENCES_APP_VERSION; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_EIP_DEFINITION; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; @@ -265,7 +267,7 @@ public class StartActivity extends Activity{ Log.e(TAG, "Couldn't show Motd. Invalid timestamp."); } } - showMainActivity(); + showVPNFragment(); } private void showMotd(Provider p, IMessage message) { @@ -277,15 +279,23 @@ public class StartActivity extends Activity{ PreferenceHelper.persistProvider(this, p); ProviderObservable.getInstance().updateProvider(p); } - //TODO: show Motd Activity! - showMainActivity(); + showMotdFragment(message); } - private void showMainActivity() { + private void showVPNFragment() { Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setAction(ACTION_SHOW_VPN_FRAGMENT); startActivity(intent); finish(); } + + private void showMotdFragment(IMessage message) { + Intent intent = new Intent(this, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setAction(ACTION_SHOW_MOTD_FRAGMENT); + intent.putExtra(EXTRA_MOTD_MSG, message.toJson()); + startActivity(intent); + finish(); + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MotdFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MotdFragment.java new file mode 100644 index 00000000..16834ab5 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MotdFragment.java @@ -0,0 +1,103 @@ +package se.leap.bitmaskclient.base.fragments; + +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; +import static se.leap.bitmaskclient.base.models.Constants.EXTRA_MOTD_MSG; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.Html; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.fragment.app.Fragment; + +import java.util.Locale; + +import de.blinkt.openvpn.core.VpnStatus; +import motd.IMessage; +import motd.Motd; +import se.leap.bitmaskclient.base.MainActivity; +import se.leap.bitmaskclient.databinding.FMotdBinding; + + +public class MotdFragment extends Fragment { + + private static final String TAG = MotdFragment.class.getSimpleName(); + private IMessage message; + FMotdBinding binding; + AppCompatTextView messageView; + AppCompatImageButton nextButton; + + public MotdFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + String messageString = getArguments().getString(EXTRA_MOTD_MSG); + if (messageString != null) { + Log.d(TAG, "MotdFragment received: " + messageString); + message = Motd.newMessage(messageString); + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + binding = FMotdBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + messageView = binding.motdContent; + nextButton = binding.nextBtn; + String currentLang = Locale.getDefault().getLanguage(); + String text = message.getLocalizedText(currentLang); + if (TextUtils.isEmpty(text)) { + text = message.getLocalizedText("en"); + } + + if (TextUtils.isEmpty(text)) { + String error = "Message of the day cannot be shown. Unsupported app language and unknown default langauge."; + Log.e(TAG, error); + VpnStatus.logError(error); + showVpnFragment(view.getContext()); + return; + } + + Log.d(TAG, "set motd text: " + text); + messageView.setText(Html.fromHtml(text)); + messageView.setMovementMethod(LinkMovementMethod.getInstance()); + nextButton.setOnClickListener(v -> { + showVpnFragment(v.getContext()); + }); + + } + + private void showVpnFragment(Context context) { + try { + Intent intent = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setAction(ACTION_SHOW_VPN_FRAGMENT); + context.startActivity(intent); + } catch (NullPointerException npe) { + npe.printStackTrace(); + } + + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index d0af8112..ee5bd2a7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -77,6 +77,7 @@ public interface Constants { String ASK_TO_CANCEL_VPN = "ask_to_cancel_vpn"; + String EXTRA_MOTD_MSG = "extra_motd_message"; ////////////////////////////////////////////// diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_right.png b/app/src/main/res/drawable-hdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..2d081384 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_arrow_right.png diff --git a/app/src/main/res/drawable-ldpi/ic_arrow_right.png b/app/src/main/res/drawable-ldpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..7fc572af --- /dev/null +++ b/app/src/main/res/drawable-ldpi/ic_arrow_right.png diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_right.png b/app/src/main/res/drawable-mdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..60702e35 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_arrow_right.png diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_right.png b/app/src/main/res/drawable-xhdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..f1f4f691 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_arrow_right.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..e0031ca8 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_arrow_right.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_right.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_right.png Binary files differnew file mode 100644 index 00000000..1b4f4239 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_arrow_right.png diff --git a/app/src/main/res/layout/f_motd.xml b/app/src/main/res/layout/f_motd.xml new file mode 100644 index 00000000..3cfe7dfe --- /dev/null +++ b/app/src/main/res/layout/f_motd.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:theme="@style/BitmaskTheme" + tools:context=".base.fragments.MotdFragment" + android:fitsSystemWindows="true"> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.1" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.9" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.85" /> + + <!-- The primary full-screen view. This can be replaced with whatever view + is needed to present your content, e.g. VideoView, SurfaceView, + TextureView, etc. --> + <ScrollView + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="@id/guideline_bottom" + app:layout_constraintLeft_toLeftOf="@+id/guideline_left" + app:layout_constraintRight_toRightOf="@+id/guideline_right" + android:clipChildren="true" + android:fillViewport="true" + > + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + > + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_sv_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.1" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_sv_center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.4" /> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/motd_icon" + android:layout_width="120dp" + android:layout_height="120dp" + app:layout_constraintTop_toTopOf="@id/guideline_sv_top" + app:layout_constraintBottom_toTopOf="@+id/guideline_sv_center" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:src="@drawable/donation_img" + android:scaleType="fitXY" + android:visibility="gone" /> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/motd_icon_circle" + android:layout_width="120dp" + android:layout_height="120dp" + app:layout_constraintTop_toTopOf="@id/guideline_sv_top" + app:layout_constraintBottom_toTopOf="@+id/guideline_sv_center" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:src="@drawable/donation_img" + android:visibility="visible" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/motd_content" + app:layout_constraintTop_toBottomOf="@+id/guideline_sv_center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/standard_margin" + android:gravity="center_horizontal" + android:keepScreenOn="true" + android:padding="@dimen/stdpadding" + android:elegantTextHeight="true" + android:autoSizeTextType="uniform" + android:text=" aaa aaa aaa aaaaaaaaaaaaaaaaaaaa \n\n\âssdfsdf Riseup's services are funded by donations from people like you. We try not to ask too often, but we have to ask sometimes. Please consider making a https://riseup.net/donate donation if you value this freely available service, appreciate that we don't track or sell your data, or want to support people around the world working towards liberatory social change." + android:textSize="16sp" + android:textStyle="bold" /> + </androidx.constraintlayout.widget.ConstraintLayout> + </ScrollView> + + <androidx.appcompat.widget.AppCompatImageButton + android:id="@+id/next_btn" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_width="match_parent" + android:layout_height="80dp" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_arrow_right" + android:text="next" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file |