Use RecyclerView, Clean up code

This commit is contained in:
MrLetsplay 2023-06-23 16:01:29 +02:00
parent a53c8c715a
commit c69f0578a1
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
16 changed files with 192 additions and 91 deletions

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_21.avd" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_21.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-06-23T13:56:20.858216724Z" />
</component>
</project>

View File

@ -1,22 +1,13 @@
package com.cringe_studios.cringe_authenticator; package com.cringe_studios.cringe_authenticator;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.cringe_studios.cringe_authenticator.databinding.ActivityIntroBinding; import com.cringe_studios.cringe_authenticator.databinding.ActivityIntroBinding;

View File

@ -29,7 +29,6 @@ import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
import com.cringe_studios.cringe_authenticator.scanner.QRScannerActivity; import com.cringe_studios.cringe_authenticator.scanner.QRScannerActivity;
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract; import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
import com.cringe_studios.cringe_authenticator.util.NavigationUtil; import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -76,8 +75,7 @@ public class MainActivity extends AppCompatActivity {
Fragment fragment = NavigationUtil.getCurrentFragment(this); Fragment fragment = NavigationUtil.getCurrentFragment(this);
if(fragment instanceof DynamicFragment) { if(fragment instanceof DynamicFragment) {
DynamicFragment frag = (DynamicFragment) fragment; DynamicFragment frag = (DynamicFragment) fragment;
SettingsUtil.addOTP(getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, MODE_PRIVATE), frag.getGroupName(), obj); frag.addOTP(obj);
frag.loadOTPs();
} }
Log.i("AMOGUS", "Actually got something bruh" + obj); Log.i("AMOGUS", "Actually got something bruh" + obj);
}); });
@ -148,10 +146,6 @@ public class MainActivity extends AppCompatActivity {
NavigationUtil.navigate(this, SettingsFragment.class, null); NavigationUtil.navigate(this, SettingsFragment.class, null);
} }
public void addCode(MenuItem item) {
// TODO: add code
}
public void scanCode(View view) { public void scanCode(View view) {
Log.i("AMOGUS", "Scan"); Log.i("AMOGUS", "Scan");
Intent intent = new Intent(this, QRScannerActivity.class); Intent intent = new Intent(this, QRScannerActivity.class);

View File

@ -1,10 +1,13 @@
package com.cringe_studios.cringe_authenticator; package com.cringe_studios.cringe_authenticator;
import androidx.annotation.NonNull;
import com.cringe_studios.cringe_authenticator_library.OTP; import com.cringe_studios.cringe_authenticator_library.OTP;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPType; import com.cringe_studios.cringe_authenticator_library.OTPType;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class OTPData implements Serializable { public class OTPData implements Serializable {
@ -16,6 +19,9 @@ public class OTPData implements Serializable {
private int period; private int period;
private int counter; private int counter;
// Cached
private OTP otp;
public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, int counter) { public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, int counter) {
this.name = name; this.name = name;
this.type = type; this.type = type;
@ -56,9 +62,11 @@ public class OTPData implements Serializable {
public OTP toOTP() { public OTP toOTP() {
// TODO: checksum // TODO: checksum
return OTP.createNewOTP(type, secret, algorithm, digits, counter, period, false); if(otp != null) return otp;
return otp = OTP.createNewOTP(type, secret, algorithm, digits, counter, period, false);
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
return "OTPData{" + return "OTPData{" +
@ -71,4 +79,17 @@ public class OTPData implements Serializable {
", counter=" + counter + ", counter=" + counter +
'}'; '}';
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OTPData otpData = (OTPData) o;
return digits == otpData.digits && period == otpData.period && counter == otpData.counter && Objects.equals(name, otpData.name) && type == otpData.type && Objects.equals(secret, otpData.secret) && algorithm == otpData.algorithm;
}
@Override
public int hashCode() {
return Objects.hash(name, type, secret, algorithm, digits, period, counter);
}
} }

View File

@ -5,7 +5,6 @@ import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -16,10 +15,10 @@ import androidx.fragment.app.Fragment;
import com.cringe_studios.cringe_authenticator.OTPData; import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.FragmentDynamicBinding; import com.cringe_studios.cringe_authenticator.databinding.FragmentDynamicBinding;
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding; import com.cringe_studios.cringe_authenticator.otplist.OTPListAdapter;
import com.cringe_studios.cringe_authenticator.otplist.OTPListItem;
import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.FabUtil;
import com.cringe_studios.cringe_authenticator.util.SettingsUtil; import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
import com.cringe_studios.cringe_authenticator_library.OTP;
import java.util.List; import java.util.List;
@ -35,6 +34,8 @@ public class DynamicFragment extends Fragment {
private Runnable refreshCodes; private Runnable refreshCodes;
private OTPListAdapter otpListAdapter;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -47,24 +48,21 @@ public class DynamicFragment extends Fragment {
groupName = requireArguments().getString(DynamicFragment.BUNDLE_GROUP); groupName = requireArguments().getString(DynamicFragment.BUNDLE_GROUP);
/*String[] totps = new String[]{"Code 1", "Code 2", groupName}; FabUtil.showFabs(requireActivity());
for(String totp : totps) {
AuthenticateTotpBinding itemBinding = AuthenticateTotpBinding.inflate(inflater); otpListAdapter = new OTPListAdapter(getContext());
itemBinding.displayName.setText(totp); binding.itemList.setAdapter(otpListAdapter);
binding.itemList.addView(itemBinding.getRoot());
}*/
loadOTPs(); loadOTPs();
FabUtil.showFabs(getActivity());
handler = new Handler(Looper.getMainLooper()); handler = new Handler(Looper.getMainLooper());
refreshCodes = () -> { refreshCodes = () -> {
for(int i = 0; i < binding.itemList.getChildCount(); i++) { for(int i = 0; i < binding.itemList.getChildCount(); i++) {
View v = binding.itemList.getChildAt(i); OTPListItem vh = (OTPListItem) binding.itemList.findViewHolderForAdapterPosition(i);
OTP otp = (OTP) v.getTag(); if(vh == null) continue;
otp.getPin(); vh.getBinding().otpCode.setText(vh.getOTPData().toOTP().getPin());
} }
handler.postDelayed(refreshCodes, 1000L); handler.postDelayed(refreshCodes, 1000L);
}; };
@ -73,21 +71,21 @@ public class DynamicFragment extends Fragment {
return binding.getRoot(); return binding.getRoot();
} }
public void loadOTPs() { private void loadOTPs() {
SharedPreferences prefs = getActivity().getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = requireActivity().getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, Context.MODE_PRIVATE);
List<OTPData> data = SettingsUtil.getOTPs(prefs, groupName); List<OTPData> data = SettingsUtil.getOTPs(prefs, groupName);
Log.i("AMOGUS", "OTPS: " + data);
binding.itemList.removeAllViews();
for(OTPData otp : data) { for(OTPData otp : data) {
OtpCodeBinding itemBinding = OtpCodeBinding.inflate(getLayoutInflater()); otpListAdapter.add(otp);
itemBinding.label.setText(otp.getName());
itemBinding.otpCode.setText(otp.toOTP().getPin());
itemBinding.getRoot().setTag(otp.toOTP());
binding.itemList.addView(itemBinding.getRoot());
} }
} }
public void addOTP(OTPData data) {
SharedPreferences prefs = requireActivity().getSharedPreferences(SettingsUtil.GROUPS_PREFS_NAME, Context.MODE_PRIVATE);
SettingsUtil.addOTP(prefs, groupName, data);
otpListAdapter.add(data);
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
@ -100,8 +98,4 @@ public class DynamicFragment extends Fragment {
super.onDestroy(); super.onDestroy();
} }
public String getGroupName() {
return groupName;
}
} }

View File

@ -16,9 +16,9 @@ public class HomeFragment extends Fragment {
private FragmentHomeBinding binding; private FragmentHomeBinding binding;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false); binding = FragmentHomeBinding.inflate(inflater, container, false);
FabUtil.hideFabs(getActivity()); FabUtil.hideFabs(requireActivity());
return binding.getRoot(); return binding.getRoot();
} }

View File

@ -4,17 +4,14 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.cringe_studios.cringe_authenticator.R;
import com.cringe_studios.cringe_authenticator.databinding.FragmentMenuBinding; import com.cringe_studios.cringe_authenticator.databinding.FragmentMenuBinding;
import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding; import com.cringe_studios.cringe_authenticator.databinding.MenuItemBinding;
import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.FabUtil;
@ -29,7 +26,7 @@ public class MenuFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentMenuBinding.inflate(inflater); binding = FragmentMenuBinding.inflate(inflater);
SharedPreferences pr = getContext().getSharedPreferences("menu", Context.MODE_PRIVATE); SharedPreferences pr = requireContext().getSharedPreferences("menu", Context.MODE_PRIVATE);
String[] items = {"a", "b"}; String[] items = {"a", "b"};
@ -42,7 +39,7 @@ public class MenuFragment extends Fragment {
NavigationUtil.navigate(this, DynamicFragment.class, bundle); NavigationUtil.navigate(this, DynamicFragment.class, bundle);
}); });
itemBinding.button.setOnLongClickListener(view -> { itemBinding.button.setOnLongClickListener(view -> {
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(requireContext())
.setTitle("Delete?") .setTitle("Delete?")
.setMessage("Delete this?") .setMessage("Delete this?")
.setPositiveButton("Yes", (dialog, which) -> itemBinding.button.setVisibility(View.GONE)) .setPositiveButton("Yes", (dialog, which) -> itemBinding.button.setVisibility(View.GONE))
@ -59,7 +56,7 @@ public class MenuFragment extends Fragment {
// TODO: edit mode // TODO: edit mode
}); });
FabUtil.hideFabs(getActivity()); FabUtil.hideFabs(requireActivity());
return binding.getRoot(); return binding.getRoot();
} }

View File

@ -7,7 +7,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
@ -30,7 +29,7 @@ public class SettingsFragment extends Fragment {
controller.navigate(R.id.FirstFragment); controller.navigate(R.id.FirstFragment);
}); });
FabUtil.hideFabs(getActivity()); FabUtil.hideFabs(requireActivity());
return binding.getRoot(); return binding.getRoot();
} }

View File

@ -0,0 +1,56 @@
package com.cringe_studios.cringe_authenticator.otplist;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
import java.util.ArrayList;
import java.util.List;
public class OTPListAdapter extends RecyclerView.Adapter<OTPListItem> {
private LayoutInflater inflater;
private List<OTPData> items;
public OTPListAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
this.items = new ArrayList<>();
}
@NonNull
@Override
public OTPListItem onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
OtpCodeBinding binding = OtpCodeBinding.inflate(inflater, parent, false);
return new OTPListItem(binding);
}
@Override
public void onBindViewHolder(@NonNull OTPListItem holder, int position) {
holder.setOTPData(items.get(position));
holder.getBinding().label.setText(holder.getOTPData().getName());
}
@Override
public int getItemCount() {
return items.size();
}
public void add(OTPData data) {
items.add(data);
notifyItemInserted(items.size() - 1);
}
public void remove(OTPData data) {
int index = items.indexOf(data);
items.remove(data);
notifyItemRemoved(index);
}
}

View File

@ -0,0 +1,32 @@
package com.cringe_studios.cringe_authenticator.otplist;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.OtpCodeBinding;
public class OTPListItem extends RecyclerView.ViewHolder {
private OtpCodeBinding binding;
private OTPData otpData;
public OTPListItem(OtpCodeBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public @NonNull OtpCodeBinding getBinding() {
return binding;
}
public void setOTPData(OTPData otpData) {
this.otpData = otpData;
}
public OTPData getOTPData() {
return otpData;
}
}

View File

@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -22,14 +21,11 @@ import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview; import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import com.cringe_studios.cringe_authenticator.OTPData; import com.cringe_studios.cringe_authenticator.OTPData;
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding; import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
import com.cringe_studios.cringe_authenticator_library.OTP;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPType; import com.cringe_studios.cringe_authenticator_library.OTPType;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.mlkit.vision.barcode.BarcodeScanner; import com.google.mlkit.vision.barcode.BarcodeScanner;
import com.google.mlkit.vision.barcode.BarcodeScannerOptions; import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
@ -37,8 +33,6 @@ import com.google.mlkit.vision.barcode.BarcodeScanning;
import com.google.mlkit.vision.barcode.common.Barcode; import com.google.mlkit.vision.barcode.common.Barcode;
import com.google.mlkit.vision.common.InputImage; import com.google.mlkit.vision.common.InputImage;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class QRScannerActivity extends AppCompatActivity { public class QRScannerActivity extends AppCompatActivity {

View File

@ -1,14 +1,12 @@
package com.cringe_studios.cringe_authenticator.util; package com.cringe_studios.cringe_authenticator.util;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.R;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
public class NavigationUtil { public class NavigationUtil {

View File

@ -29,7 +29,7 @@ public class SettingsUtil {
otps.add(data); otps.add(data);
prefs.edit() prefs.edit()
.putString("group." + group, GSON.toJson(otps.toArray(new OTPData[otps.size()]))) .putString("group." + group, GSON.toJson(otps.toArray(new OTPData[0])))
.apply(); .apply();
} }

View File

@ -4,20 +4,22 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".fragment.SecondFragment"> tools:context=".fragment.DynamicFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="16dp"> android:padding="16dp">
<LinearLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/itemList" android:id="@+id/itemList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:orientation="vertical" android:orientation="vertical"
tools:layout_editor_absoluteX="16dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_editor_absoluteY="16dp"></LinearLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -19,7 +19,7 @@
android:text="Enable something" android:text="Enable something"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="16dp" /> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@ -2,14 +2,10 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/rectangle" android:background="@drawable/rectangle"
android:layout_margin="5dp" android:layout_margin="5dp"
android:padding="5dp" android:padding="5dp">
android:orientation="vertical">
<TextView <TextView
android:id="@+id/label" android:id="@+id/label"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -26,4 +22,3 @@
android:textAlignment="center" android:textAlignment="center"
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
</LinearLayout>