mirror of
https://github.com/Team4388/RidgeScout.git
synced 2026-06-09 00:37:59 -06:00
Add scout notice, start work on scouting report system
This commit is contained in:
@@ -73,7 +73,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
appBarConfiguration = new AppBarConfiguration.Builder(
|
||||
R.id.navigation_scouting,
|
||||
R.id.navigation_data,
|
||||
R.id.navigation_data_parent,
|
||||
R.id.navigation_transfer,
|
||||
R.id.navigation_settings)
|
||||
.build();
|
||||
|
||||
@@ -120,6 +120,18 @@ public class frcEvent {
|
||||
return maxMatch;
|
||||
}
|
||||
|
||||
public frcMatch getNextTeamMatch(int teamNum, int curMatch){
|
||||
frcMatch[] teamMatches = getTeamMatches(teamNum);
|
||||
|
||||
for(int i = 0; i < teamMatches.length; i++) {
|
||||
if (teamMatches[i].matchIndex > curMatch)
|
||||
return teamMatches[i];
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// public
|
||||
|
||||
// Returns the soonest match that there will be all the possible upcoming data on other teams
|
||||
@@ -144,4 +156,13 @@ public class frcEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public frcTeam getTeamByNum(int teamNum){
|
||||
for(int i = 0; i < teams.size(); i++){
|
||||
frcTeam team = teams.get(i);
|
||||
if(team.teamNumber == teamNum)
|
||||
return team;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,8 +200,13 @@ public class CandlestickView extends View {
|
||||
teamDataArray[i] = (int) teamData.get(i).get();
|
||||
}
|
||||
|
||||
float lowerQuartile = DataProcessing.calculatePercentile(teamDataArray, 25);
|
||||
float upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75);
|
||||
float lowerQuartile = 0;
|
||||
float upperQuartile = 0;
|
||||
|
||||
if(teamDataArray.length != 0) {
|
||||
lowerQuartile = DataProcessing.calculatePercentile(teamDataArray, 25);
|
||||
upperQuartile = DataProcessing.calculatePercentile(teamDataArray, 75);
|
||||
}
|
||||
|
||||
System.out.println(locmin + ", " + lowerQuartile + ", " + avg + ", " + upperQuartile + ", " + locmax);
|
||||
setData(locmin, lowerQuartile, avg, upperQuartile, locmax, absmin, absmax);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.ridgebotics.ridgescout.R;
|
||||
import com.ridgebotics.ridgescout.types.frcTeam;
|
||||
@@ -84,9 +85,6 @@ public class DataFragment extends Fragment {
|
||||
}
|
||||
|
||||
public void load_teams(){
|
||||
DataManager.reload_event();
|
||||
|
||||
if(event == null) return;
|
||||
|
||||
int[] teamNums = new int[event.teams.size()];
|
||||
|
||||
@@ -113,7 +111,8 @@ public class DataFragment extends Fragment {
|
||||
frcTeam finalTeam = team;
|
||||
teamRow.setOnClickListener(v -> {
|
||||
TeamsFragment.setTeam(finalTeam);
|
||||
findNavController(this).navigate(R.id.action_navigation_data_to_navigation_data_teams);
|
||||
((DataParentFragment) getParentFragment()).moveToFragment(new TeamsFragment());
|
||||
// findNavController(this).navigate(R.id.action_navigation_data_parent_to_navigation_data_teams);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -131,7 +130,8 @@ public class DataFragment extends Fragment {
|
||||
final int fi = i;
|
||||
tr.setOnClickListener(v -> {
|
||||
FieldDataFragment.setFieldIndex(fi);
|
||||
findNavController(this).navigate(R.id.action_navigation_data_to_navigation_data_field_data);
|
||||
((DataParentFragment) getParentFragment()).moveToFragment(new FieldDataFragment());
|
||||
// findNavController(get).navigate(R.id.action_navigation_data_parent_to_navigation_data_field_data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.ridgebotics.ridgescout.ui.data;
|
||||
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.navigation.fragment.FragmentKt.findNavController;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.datafragment_option_1;
|
||||
import static com.ridgebotics.ridgescout.utility.Colors.datafragment_option_2;
|
||||
import static com.ridgebotics.ridgescout.utility.DataManager.evcode;
|
||||
import static com.ridgebotics.ridgescout.utility.DataManager.event;
|
||||
import static com.ridgebotics.ridgescout.utility.DataManager.match_latest_values;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.ridgebotics.ridgescout.R;
|
||||
import com.ridgebotics.ridgescout.databinding.FragmentDataParentBinding;
|
||||
import com.ridgebotics.ridgescout.types.frcMatch;
|
||||
import com.ridgebotics.ridgescout.types.frcTeam;
|
||||
import com.ridgebotics.ridgescout.ui.FieldBorderedRow;
|
||||
import com.ridgebotics.ridgescout.ui.TeamListOption;
|
||||
import com.ridgebotics.ridgescout.utility.DataManager;
|
||||
import com.ridgebotics.ridgescout.utility.SettingsManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DataParentFragment extends Fragment {
|
||||
|
||||
private FragmentDataParentBinding binding;
|
||||
|
||||
private DataFragment dataFragment;
|
||||
|
||||
private boolean editBoxEnabled = true;
|
||||
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
binding = FragmentDataParentBinding.inflate(inflater, container, false);
|
||||
DataManager.reload_event();
|
||||
|
||||
if (savedInstanceState == null && dataFragment == null){
|
||||
dataFragment = new DataFragment();
|
||||
|
||||
//add child fragment
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.data_subfragment, dataFragment, "Data Subfragment")
|
||||
.commit();
|
||||
}
|
||||
|
||||
if(evcode.equals("unset") || event == null){
|
||||
binding.reportToggleButton.setVisibility(GONE);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
binding.reportToggleButton.setOnClickListener(view -> {
|
||||
editBoxEnabled =! editBoxEnabled;
|
||||
binding.ScoutingEditBox.setVisibility(editBoxEnabled ? GONE : VISIBLE);
|
||||
binding.reportToggleButton.setText(editBoxEnabled ? "▲ report" : "▼ report");
|
||||
});
|
||||
|
||||
generateScoutingTemplate(SettingsManager.getMatchNum());
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
public void moveToFragment(Fragment newFragment){
|
||||
// consider using Java coding conventions (upper first char class names!!!)
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
// Replace whatever is in the fragment_container view with this fragment,
|
||||
// and add the transaction to the back stack
|
||||
transaction.replace(R.id.data_subfragment, newFragment);
|
||||
transaction.addToBackStack(null);
|
||||
// Commit the transaction
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
|
||||
// Generate format for scouting data
|
||||
public void generateScoutingTemplate(int currentMatch){
|
||||
|
||||
int teamNum = SettingsManager.getTeamNum();
|
||||
boolean isBlueAlliance = false;
|
||||
|
||||
|
||||
frcMatch nextMatch = event.getNextTeamMatch(teamNum, currentMatch);
|
||||
|
||||
|
||||
List<frcTeam> ourAlliance = new ArrayList<>();
|
||||
List<frcTeam> opposingAlliance = new ArrayList<>();
|
||||
|
||||
for(int a = 0; a < nextMatch.blueAlliance.length; a++)
|
||||
if(nextMatch.blueAlliance[a] != teamNum){
|
||||
(!isBlueAlliance ? ourAlliance : opposingAlliance).add(event.getTeamByNum(nextMatch.blueAlliance[a]));
|
||||
}
|
||||
for(int a = 0; a < nextMatch.redAlliance.length; a++)
|
||||
if(nextMatch.redAlliance[a] != teamNum){
|
||||
(isBlueAlliance ? ourAlliance : opposingAlliance).add(event.getTeamByNum(nextMatch.redAlliance[a]));
|
||||
}
|
||||
|
||||
|
||||
String output = "Match: " + (nextMatch.matchIndex+1) + "\n";
|
||||
|
||||
output += "## Our Alliance ##";
|
||||
output += getTeamNameAndNum(ourAlliance.get(0));
|
||||
output += getTeamNameAndNum(ourAlliance.get(1));
|
||||
output += "\n## Opposing Alliance ##";
|
||||
output += getTeamNameAndNum(opposingAlliance.get(0));
|
||||
output += getTeamNameAndNum(opposingAlliance.get(1));
|
||||
output += getTeamNameAndNum(opposingAlliance.get(2));
|
||||
|
||||
binding.scoutingReportEdittext.setText(output);
|
||||
|
||||
}
|
||||
|
||||
private static String getTeamNameAndNum(frcTeam team){
|
||||
return "\n" + team.teamNumber + " " + team.teamName + ": \n";
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,11 @@ public class MatchScoutingFragment extends Fragment {
|
||||
create_fields();
|
||||
update_scouting_data();
|
||||
|
||||
if(DataManager.scoutNotice.isEmpty())
|
||||
binding.scoutingNoticeBox.setVisibility(View.GONE);
|
||||
else
|
||||
binding.scoutingNoticeText.setText(DataManager.scoutNotice);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ public class PitScoutingFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
if(DataManager.scoutNotice.isEmpty())
|
||||
binding.scoutingNoticeBox.setVisibility(View.GONE);
|
||||
else
|
||||
binding.scoutingNoticeText.setText(DataManager.scoutNotice);
|
||||
|
||||
loadTeam();
|
||||
|
||||
|
||||
@@ -124,16 +124,11 @@ public class ScoutingFragment extends Fragment {
|
||||
|
||||
binding.textName.setText("Welcome, " + SettingsManager.getUsername() + "!");
|
||||
|
||||
int nextMatch = -1;
|
||||
int teamNum = SettingsManager.getTeamNum();
|
||||
for(int i = SettingsManager.getMatchNum(); i < event.matches.size(); i++){ // Loop through matches and find next match
|
||||
boolean foundMatch = false;
|
||||
for(int a = 0; a < 3; a++) if(event.matches.get(i).blueAlliance[a] == teamNum){nextMatch = i+1; foundMatch = true;}
|
||||
for(int a = 0; a < 3; a++) if(event.matches.get(i).redAlliance[a] == teamNum){nextMatch = i+1; foundMatch = true;}
|
||||
if(foundMatch) break;
|
||||
}
|
||||
int matchNum = SettingsManager.getMatchNum();
|
||||
int nextMatch = event.getNextTeamMatch(SettingsManager.getTeamNum(), matchNum).matchIndex;
|
||||
|
||||
binding.textNextMatch.setText("Our next match: Match " + nextMatch);
|
||||
binding.textMatchAlliance.setText("Match: " + (SettingsManager.getMatchNum()+1) + ", " + SettingsManager.getAllyPos());
|
||||
binding.textMatchAlliance.setText("Match: " + (matchNum+1) + ", " + SettingsManager.getAllyPos());
|
||||
binding.textRescoutIndicator.setText("Things to rescout: " + DataManager.rescout_list.size());
|
||||
|
||||
return binding.getRoot();
|
||||
|
||||
@@ -16,7 +16,9 @@ import static com.ridgebotics.ridgescout.utility.SettingsManager.defaults;
|
||||
import static com.ridgebotics.ridgescout.utility.SettingsManager.getEditor;
|
||||
import static com.ridgebotics.ridgescout.utility.SettingsManager.prefs;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
@@ -25,7 +27,10 @@ import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -130,6 +135,14 @@ public class SettingsFragment extends Fragment {
|
||||
|
||||
binding.SettingsTable.removeAllViews();
|
||||
manager.getView(binding.SettingsTable);
|
||||
|
||||
if(!DataManager.getevcode().equals("unset")){
|
||||
Button editNoticeButton = new Button(getContext());
|
||||
editNoticeButton.setText("Edit Scout Notice");
|
||||
binding.SettingsTable.addView(editNoticeButton);
|
||||
editNoticeButton.setOnClickListener(v->editNotice());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +154,27 @@ public class SettingsFragment extends Fragment {
|
||||
}
|
||||
|
||||
|
||||
private void editNotice(){
|
||||
ScrollView sv = new ScrollView(getContext());
|
||||
EditText editText = new EditText(getContext());
|
||||
editText.setText(DataManager.scoutNotice);
|
||||
sv.addView(editText);
|
||||
|
||||
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
|
||||
alert.setTitle("Edit Notice");
|
||||
alert.setView(sv);
|
||||
alert.setNeutralButton("Cancel", null);
|
||||
alert.setPositiveButton("Save", (dialogInterface, i) -> {
|
||||
DataManager.scoutNotice = editText.getText().toString();
|
||||
DataManager.save_scout_notice();
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
|
||||
alert.create().show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ public class DataManager {
|
||||
SettingsManager.setEVCode("unset");
|
||||
evcode = "unset";
|
||||
}else{
|
||||
AlertManager.toast("Reloaded event!");
|
||||
reload_rescout_list();
|
||||
reload_scout_notice();
|
||||
AlertManager.toast("Reloaded event!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +91,36 @@ public class DataManager {
|
||||
AlertManager.error("Error saving scout fields", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String scoutNotice = "";
|
||||
|
||||
public static void reload_scout_notice(){
|
||||
if(!FileEditor.fileExist(evcode + ".scoutnotice")) {scoutNotice = ""; return;}
|
||||
byte[] file = FileEditor.readFile(evcode + ".scoutnotice");
|
||||
if(file == null) {scoutNotice = ""; return;}
|
||||
|
||||
try {
|
||||
BuiltByteParser bbp = new BuiltByteParser(file);
|
||||
scoutNotice = (String) (bbp.parse().get(0).get());
|
||||
|
||||
} catch (Exception e){
|
||||
AlertManager.error("Error loading scout notice", e);
|
||||
rescout_list = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void save_scout_notice() {
|
||||
try {
|
||||
if(scoutNotice.isEmpty()){
|
||||
FileEditor.deleteFile(evcode + ".scoutnotice");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuilder bb = new ByteBuilder();
|
||||
bb.addString(scoutNotice);
|
||||
FileEditor.writeFile(evcode + ".scoutnotice", bb.build());
|
||||
} catch (Exception e){
|
||||
AlertManager.error("Error saving scout notice", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TableLayout
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TableLayout
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.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">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/data_subfragment"
|
||||
android:name="com.ridgebotics.ridgescout.ui.data.DataFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/ScoutingEditBox"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/ScoutingEditBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/scouting_report_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/report_toggle_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="▲ report"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/ScoutingEditBox"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -14,6 +14,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="50dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
@@ -18,6 +18,26 @@
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="48dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/scouting_notice_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/border"
|
||||
android:padding="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scouting_notice_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:text="Scouting Notice">
|
||||
</TextView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.ridgebotics.ridgescout.ui.TeamCard
|
||||
android:id="@+id/match_team_card"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -53,6 +53,26 @@
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="48dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/scouting_notice_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/border"
|
||||
android:padding="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scouting_notice_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:text="Scouting Notice">
|
||||
</TextView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.ridgebotics.ridgescout.ui.TeamCard
|
||||
android:id="@+id/pits_team_card"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:icon="@drawable/scouting"
|
||||
android:title="@string/title_scouting" />
|
||||
<item
|
||||
android:id="@+id/navigation_data"
|
||||
android:id="@+id/navigation_data_parent"
|
||||
android:icon="@drawable/data"
|
||||
android:title="@string/title_data" />
|
||||
<item
|
||||
|
||||
@@ -89,6 +89,18 @@
|
||||
|
||||
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_data_parent"
|
||||
android:name="com.ridgebotics.ridgescout.ui.data.DataParentFragment"
|
||||
android:label="@string/title_data"
|
||||
tools:layout="@layout/fragment_data">
|
||||
<action
|
||||
android:id="@+id/action_navigation_data_parent_to_navigation_data_field_data"
|
||||
app:destination="@id/navigation_data_field_data" />
|
||||
<action
|
||||
android:id="@+id/action_navigation_data_parent_to_navigation_data_teams"
|
||||
app:destination="@id/navigation_data_teams" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/navigation_data"
|
||||
|
||||
Reference in New Issue
Block a user