Refresh onboarding cards.

main
Alex Hart 2023-02-27 13:12:32 -04:00 zatwierdzone przez Nicholas Tinsley
rodzic 6bc5b19b1e
commit a9c4fcf894
12 zmienionych plików z 124 dodań i 614 usunięć

Wyświetl plik

@ -4,9 +4,6 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.LocaleFeatureFlags;
import org.thoughtcrime.securesms.util.Util;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -14,7 +11,6 @@ public final class OnboardingValues extends SignalStoreValues {
private static final String SHOW_NEW_GROUP = "onboarding.new_group"; private static final String SHOW_NEW_GROUP = "onboarding.new_group";
private static final String SHOW_INVITE_FRIENDS = "onboarding.invite_friends"; private static final String SHOW_INVITE_FRIENDS = "onboarding.invite_friends";
private static final String SHOW_SMS = "onboarding.sms";
private static final String SHOW_APPEARANCE = "onboarding.appearance"; private static final String SHOW_APPEARANCE = "onboarding.appearance";
private static final String SHOW_ADD_PHOTO = "onboarding.add_photo"; private static final String SHOW_ADD_PHOTO = "onboarding.add_photo";
@ -26,7 +22,6 @@ public final class OnboardingValues extends SignalStoreValues {
void onFirstEverAppLaunch() { void onFirstEverAppLaunch() {
putBoolean(SHOW_NEW_GROUP, true); putBoolean(SHOW_NEW_GROUP, true);
putBoolean(SHOW_INVITE_FRIENDS, true); putBoolean(SHOW_INVITE_FRIENDS, true);
putBoolean(SHOW_SMS, true);
putBoolean(SHOW_APPEARANCE, true); putBoolean(SHOW_APPEARANCE, true);
putBoolean(SHOW_ADD_PHOTO, true); putBoolean(SHOW_ADD_PHOTO, true);
} }
@ -39,7 +34,6 @@ public final class OnboardingValues extends SignalStoreValues {
public void clearAll() { public void clearAll() {
setShowNewGroup(false); setShowNewGroup(false);
setShowInviteFriends(false); setShowInviteFriends(false);
setShowSms(false);
setShowAppearance(false); setShowAppearance(false);
setShowAddPhoto(false); setShowAddPhoto(false);
} }
@ -47,7 +41,6 @@ public final class OnboardingValues extends SignalStoreValues {
public boolean hasOnboarding(@NonNull Context context) { public boolean hasOnboarding(@NonNull Context context) {
return shouldShowNewGroup() || return shouldShowNewGroup() ||
shouldShowInviteFriends() || shouldShowInviteFriends() ||
shouldShowSms() ||
shouldShowAppearance() || shouldShowAppearance() ||
shouldShowAddPhoto(); shouldShowAddPhoto();
} }
@ -68,14 +61,6 @@ public final class OnboardingValues extends SignalStoreValues {
return getBoolean(SHOW_INVITE_FRIENDS, false); return getBoolean(SHOW_INVITE_FRIENDS, false);
} }
public void setShowSms(boolean value) {
putBoolean(SHOW_SMS, value);
}
public boolean shouldShowSms() {
return false;
}
public void setShowAppearance(boolean value) { public void setShowAppearance(boolean value) {
putBoolean(SHOW_APPEARANCE, value); putBoolean(SHOW_APPEARANCE, value);
} }
@ -88,7 +73,7 @@ public final class OnboardingValues extends SignalStoreValues {
putBoolean(SHOW_ADD_PHOTO, value); putBoolean(SHOW_ADD_PHOTO, value);
} }
public boolean shouldShowAddPhoto(){ public boolean shouldShowAddPhoto() {
return getBoolean(SHOW_ADD_PHOTO, false); return getBoolean(SHOW_ADD_PHOTO, false);
} }
} }

Wyświetl plik

@ -7,23 +7,21 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.InviteActivity; import org.thoughtcrime.securesms.InviteActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment; import org.thoughtcrime.securesms.databinding.OnboardingMegaphoneCardBinding;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity; import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity;
import org.thoughtcrime.securesms.util.SmsUtil;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity; import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
import java.util.ArrayList; import java.util.ArrayList;
@ -56,7 +54,6 @@ public class OnboardingMegaphoneView extends FrameLayout {
} }
public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener) { public void present(@NonNull Megaphone megaphone, @NonNull MegaphoneActionController listener) {
this.cardList.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
this.cardList.setAdapter(new CardAdapter(getContext(), listener)); this.cardList.setAdapter(new CardAdapter(getContext(), listener));
} }
@ -64,9 +61,8 @@ public class OnboardingMegaphoneView extends FrameLayout {
private static final int TYPE_GROUP = 0; private static final int TYPE_GROUP = 0;
private static final int TYPE_INVITE = 1; private static final int TYPE_INVITE = 1;
private static final int TYPE_SMS = 2; private static final int TYPE_APPEARANCE = 2;
private static final int TYPE_APPEARANCE = 3; private static final int TYPE_ADD_PHOTO = 3;
private static final int TYPE_ADD_PHOTO = 4;
private final Context context; private final Context context;
private final MegaphoneActionController controller; private final MegaphoneActionController controller;
@ -75,7 +71,7 @@ public class OnboardingMegaphoneView extends FrameLayout {
CardAdapter(@NonNull Context context, @NonNull MegaphoneActionController controller) { CardAdapter(@NonNull Context context, @NonNull MegaphoneActionController controller) {
this.context = context; this.context = context;
this.controller = controller; this.controller = controller;
this.data = buildData(context); this.data = buildData();
if (data.isEmpty()) { if (data.isEmpty()) {
Log.i(TAG, "Nothing to show (constructor)! Considering megaphone completed."); Log.i(TAG, "Nothing to show (constructor)! Considering megaphone completed.");
@ -97,11 +93,10 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
public @NonNull CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public @NonNull CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.onboarding_megaphone_list_item, parent, false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.onboarding_megaphone_card, parent, false);
switch (viewType) { switch (viewType) {
case TYPE_GROUP: return new GroupCardViewHolder(view); case TYPE_GROUP: return new GroupCardViewHolder(view);
case TYPE_INVITE: return new InviteCardViewHolder(view); case TYPE_INVITE: return new InviteCardViewHolder(view);
case TYPE_SMS: return new SmsCardViewHolder(view);
case TYPE_APPEARANCE: return new AppearanceCardViewHolder(view); case TYPE_APPEARANCE: return new AppearanceCardViewHolder(view);
case TYPE_ADD_PHOTO: return new AddPhotoCardViewHolder(view); case TYPE_ADD_PHOTO: return new AddPhotoCardViewHolder(view);
default: throw new IllegalStateException("Invalid viewType! " + viewType); default: throw new IllegalStateException("Invalid viewType! " + viewType);
@ -121,7 +116,7 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
public void onClick() { public void onClick() {
data.clear(); data.clear();
data.addAll(buildData(context)); data.addAll(buildData());
if (data.isEmpty()) { if (data.isEmpty()) {
Log.i(TAG, "Nothing to show! Considering megaphone completed."); Log.i(TAG, "Nothing to show! Considering megaphone completed.");
controller.onMegaphoneCompleted(Megaphones.Event.ONBOARDING); controller.onMegaphoneCompleted(Megaphones.Event.ONBOARDING);
@ -129,7 +124,7 @@ public class OnboardingMegaphoneView extends FrameLayout {
notifyDataSetChanged(); notifyDataSetChanged();
} }
private static List<Integer> buildData(@NonNull Context context) { private static List<Integer> buildData() {
List<Integer> data = new ArrayList<>(); List<Integer> data = new ArrayList<>();
if (SignalStore.onboarding().shouldShowNewGroup()) { if (SignalStore.onboarding().shouldShowNewGroup()) {
@ -148,10 +143,6 @@ public class OnboardingMegaphoneView extends FrameLayout {
data.add(TYPE_APPEARANCE); data.add(TYPE_APPEARANCE);
} }
if (SignalStore.onboarding().shouldShowSms()) {
data.add(TYPE_SMS);
}
return data; return data;
} }
} }
@ -161,25 +152,22 @@ public class OnboardingMegaphoneView extends FrameLayout {
} }
private static abstract class CardViewHolder extends RecyclerView.ViewHolder { private static abstract class CardViewHolder extends RecyclerView.ViewHolder {
private final ImageView image; private final OnboardingMegaphoneCardBinding binding;
private final TextView actionButton;
private final View closeButton;
public CardViewHolder(@NonNull View itemView) { public CardViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
this.image = itemView.findViewById(R.id.onboarding_megaphone_item_image); binding = OnboardingMegaphoneCardBinding.bind(itemView);
this.actionButton = itemView.findViewById(R.id.onboarding_megaphone_item_button);
this.closeButton = itemView.findViewById(R.id.onboarding_megaphone_item_close);
} }
public void bind(@NonNull ActionClickListener listener, @NonNull MegaphoneActionController controller) { public void bind(@NonNull ActionClickListener listener, @NonNull MegaphoneActionController controller) {
image.setImageResource(getImageRes()); binding.getRoot().setCardBackgroundColor(ContextCompat.getColor(binding.getRoot().getContext(), getBackgroundColor()));
actionButton.setText(getButtonStringRes()); binding.icon.setImageResource(getImageRes());
actionButton.setOnClickListener(v -> { binding.text.setText(getButtonStringRes());
binding.getRoot().setOnClickListener(v -> {
onActionClicked(controller); onActionClicked(controller);
listener.onClick(); listener.onClick();
}); });
closeButton.setOnClickListener(v -> { binding.close.setOnClickListener(v -> {
onCloseClicked(); onCloseClicked();
listener.onClick(); listener.onClick();
}); });
@ -187,6 +175,7 @@ public class OnboardingMegaphoneView extends FrameLayout {
abstract @StringRes int getButtonStringRes(); abstract @StringRes int getButtonStringRes();
abstract @DrawableRes int getImageRes(); abstract @DrawableRes int getImageRes();
abstract @ColorRes int getBackgroundColor();
abstract void onActionClicked(@NonNull MegaphoneActionController controller); abstract void onActionClicked(@NonNull MegaphoneActionController controller);
abstract void onCloseClicked(); abstract void onCloseClicked();
} }
@ -204,7 +193,12 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
int getImageRes() { int getImageRes() {
return R.drawable.ic_megaphone_start_group; return R.drawable.symbol_group_24;
}
@Override
int getBackgroundColor() {
return R.color.onboarding_background_1;
} }
@Override @Override
@ -231,7 +225,12 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
int getImageRes() { int getImageRes() {
return R.drawable.ic_megaphone_invite_friends; return R.drawable.symbol_invite_24;
}
@Override
int getBackgroundColor() {
return R.color.onboarding_background_2;
} }
@Override @Override
@ -245,35 +244,6 @@ public class OnboardingMegaphoneView extends FrameLayout {
} }
} }
private static class SmsCardViewHolder extends CardViewHolder {
public SmsCardViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
int getButtonStringRes() {
return R.string.Megaphones_use_sms;
}
@Override
int getImageRes() {
return R.drawable.ic_megaphone_use_sms;
}
@Override
void onActionClicked(@NonNull MegaphoneActionController controller) {
Intent intent = SmsUtil.getSmsRoleIntent(controller.getMegaphoneActivity());
controller.onMegaphoneNavigationRequested(intent, ConversationListFragment.SMS_ROLE_REQUEST_CODE);
SignalStore.onboarding().setShowSms(false);
}
@Override
void onCloseClicked() {
SignalStore.onboarding().setShowSms(false);
}
}
private static class AppearanceCardViewHolder extends CardViewHolder { private static class AppearanceCardViewHolder extends CardViewHolder {
public AppearanceCardViewHolder(@NonNull View itemView) { public AppearanceCardViewHolder(@NonNull View itemView) {
@ -282,12 +252,17 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
int getButtonStringRes() { int getButtonStringRes() {
return R.string.Megaphones_appearance; return R.string.Megaphones_chat_colors;
} }
@Override @Override
int getImageRes() { int getImageRes() {
return R.drawable.ic_signal_appearance; return R.drawable.ic_color_24;
}
@Override
int getBackgroundColor() {
return R.color.onboarding_background_3;
} }
@Override @Override
@ -310,12 +285,17 @@ public class OnboardingMegaphoneView extends FrameLayout {
@Override @Override
int getButtonStringRes() { int getButtonStringRes() {
return R.string.Megaphones_add_photo; return R.string.Megaphones_add_a_profile_photo;
} }
@Override @Override
int getImageRes() { int getImageRes() {
return R.drawable.ic_signal_add_photo; return R.drawable.symbol_person_circle_24;
}
@Override
int getBackgroundColor() {
return R.color.onboarding_background_4;
} }
@Override @Override

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1,22 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
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="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
tools:parentTag="android.widget.FrameLayout"> android:paddingBottom="10dp"
tools:parentTag="android.widget.FrameLayout"
tools:viewBindingIgnore="true">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
android:clipChildren="false"> android:orientation="vertical">
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -26,18 +25,18 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="12dp" android:background="@color/signal_background_primary"
android:clipToPadding="false"
android:clipChildren="false" android:clipChildren="false"
android:background="@color/signal_background_primary"> android:clipToPadding="false"
android:paddingBottom="12dp">
<TextView <TextView
android:id="@+id/onboarding_megaphone_title" android:id="@+id/onboarding_megaphone_title"
style="@style/TextAppearance.Signal.Title2.Bold"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
style="@style/TextAppearance.Signal.Title2.Bold"
android:text="@string/Megaphones_get_started" android:text="@string/Megaphones_get_started"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -49,7 +48,10 @@
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/onboarding_megaphone_title"/> android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/onboarding_megaphone_title"
tools:listitem="@layout/onboarding_megaphone_card" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="152dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
app:cardCornerRadius="28dp"
app:cardElevation="0dp"
tools:cardBackgroundColor="@color/onboarding_background_1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="84dp">
<ImageView
android:id="@+id/close"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/Material3SearchToolbar__close"
android:scaleType="centerInside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/symbol_x_24"
app:tint="@color/signal_light_colorOutline" />
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:importantForAccessibility="no"
android:scaleType="centerInside"
app:layout_constraintBottom_toTopOf="@id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:tint="@color/signal_light_colorOnSurface"
tools:srcCompat="@drawable/symbol_group_24" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Signal.Text.LabelMedium"
android:textColor="@color/signal_light_colorOnSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/icon"
tools:text="New Group" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

Wyświetl plik

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardBackgroundColor="@color/megaphone_background_color"
app:cardCornerRadius="12dp"
app:cardElevation="6dp"
app:contentPadding="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/onboarding_megaphone_item_image"
android:layout_width="match_parent"
android:layout_height="90dp"
android:scaleType="centerCrop"
tools:srcCompat="@drawable/ic_megaphone_start_group"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/onboarding_megaphone_item_button"
style="@style/Signal.Widget.Button.Small.Primary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
tools:text="@string/Megaphones_new_group"
app:layout_constraintStart_toStartOf="@id/onboarding_megaphone_item_image"
app:layout_constraintEnd_toEndOf="@id/onboarding_megaphone_item_image"
app:layout_constraintTop_toBottomOf="@id/onboarding_megaphone_item_image"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/onboarding_megaphone_item_close"
android:layout_marginEnd="5dp"
android:layout_marginTop="5dp"
style="@style/Widget.Signal.Button.Icon.Circular.Small"
app:backgroundTint="@color/transparent_black_40"
app:icon="@drawable/ic_close_14"
app:iconTint="@color/core_white"
app:rippleColor="@color/core_ultramarine"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

Wyświetl plik

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="onboarding_background_1">#FFF6EDE0</color>
<color name="onboarding_background_2">#FFDEE5D6</color>
<color name="onboarding_background_3">#FFD6E5E5</color>
<color name="onboarding_background_4">#FFE5DBE7</color>
</resources>

Wyświetl plik

@ -1167,8 +1167,8 @@
<string name="Megaphones_new_group">New group</string> <string name="Megaphones_new_group">New group</string>
<string name="Megaphones_invite_friends">Invite friends</string> <string name="Megaphones_invite_friends">Invite friends</string>
<string name="Megaphones_use_sms">Use SMS</string> <string name="Megaphones_use_sms">Use SMS</string>
<string name="Megaphones_appearance">Appearance</string> <string name="Megaphones_chat_colors">Chat colors</string>
<string name="Megaphones_add_photo">Add photo</string> <string name="Megaphones_add_a_profile_photo">Add a profile photo</string>
<!-- Title of a bottom sheet to render messages that all quote a specific message --> <!-- Title of a bottom sheet to render messages that all quote a specific message -->
<string name="MessageQuotesBottomSheet_replies">Replies</string> <string name="MessageQuotesBottomSheet_replies">Replies</string>