That fuck shit the fascists are using
1package org.tm.archive.logsubmit;
2
3import android.app.Activity;
4import android.content.Intent;
5import android.net.Uri;
6import android.os.Bundle;
7import android.text.SpannableString;
8import android.text.Spanned;
9import android.text.style.URLSpan;
10import android.text.util.Linkify;
11import android.view.Menu;
12import android.view.MenuItem;
13import android.view.View;
14import android.widget.TextView;
15import android.widget.Toast;
16
17import androidx.annotation.NonNull;
18import androidx.annotation.Nullable;
19import androidx.appcompat.app.AlertDialog;
20import androidx.appcompat.widget.SearchView;
21import androidx.core.app.ShareCompat;
22import androidx.core.text.util.LinkifyCompat;
23import androidx.lifecycle.ViewModelProvider;
24import androidx.recyclerview.widget.LinearLayoutManager;
25import androidx.recyclerview.widget.RecyclerView;
26
27import com.google.android.material.dialog.MaterialAlertDialogBuilder;
28
29import org.tm.archive.BaseActivity;
30import org.tm.archive.R;
31import org.tm.archive.components.ProgressCard;
32import org.tm.archive.util.DynamicTheme;
33import org.tm.archive.util.LongClickCopySpan;
34import org.tm.archive.util.LongClickMovementMethod;
35import org.tm.archive.util.ThemeUtil;
36import org.tm.archive.util.ViewUtil;
37import org.tm.archive.util.views.CircularProgressMaterialButton;
38
39import java.util.List;
40
41public class SubmitDebugLogActivity extends BaseActivity implements SubmitDebugLogAdapter.Listener {
42
43 private static final int CODE_SAVE = 24601;
44
45 private RecyclerView lineList;
46 private SubmitDebugLogAdapter adapter;
47 private SubmitDebugLogViewModel viewModel;
48
49 private View warningBanner;
50 private View editBanner;
51 private CircularProgressMaterialButton submitButton;
52 private View scrollToBottomButton;
53 private View scrollToTopButton;
54 private ProgressCard progressCard;
55
56 private MenuItem editMenuItem;
57 private MenuItem doneMenuItem;
58 private MenuItem searchMenuItem;
59 private MenuItem saveMenuItem;
60
61 private final DynamicTheme dynamicTheme = new DynamicTheme();
62
63 @Override
64 protected void onCreate(Bundle savedInstanceState) {
65 super.onCreate(savedInstanceState);
66 dynamicTheme.onCreate(this);
67 setContentView(R.layout.submit_debug_log_activity);
68 getSupportActionBar().setDisplayHomeAsUpEnabled(true);
69 getSupportActionBar().setTitle(R.string.HelpSettingsFragment__debug_log);
70
71 this.viewModel = new ViewModelProvider(this, new SubmitDebugLogViewModel.Factory()).get(SubmitDebugLogViewModel.class);
72
73 initView();
74 initViewModel();
75 }
76
77 @Override
78 protected void onResume() {
79 super.onResume();
80 dynamicTheme.onResume(this);
81 }
82
83 @Override
84 public boolean onCreateOptionsMenu(Menu menu) {
85 getMenuInflater().inflate(R.menu.submit_debug_log_normal, menu);
86
87 this.editMenuItem = menu.findItem(R.id.menu_edit_log);
88 this.doneMenuItem = menu.findItem(R.id.menu_done_editing_log);
89 this.searchMenuItem = menu.findItem(R.id.menu_search);
90 this.saveMenuItem = menu.findItem(R.id.menu_save);
91
92 SearchView searchView = (SearchView) searchMenuItem.getActionView();
93 SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
94 @Override
95 public boolean onQueryTextSubmit(String query) {
96 viewModel.onQueryUpdated(query);
97 return true;
98 }
99
100 @Override
101 public boolean onQueryTextChange(String query) {
102 viewModel.onQueryUpdated(query);
103 return true;
104 }
105 };
106
107 searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
108 @Override
109 public boolean onMenuItemActionExpand(MenuItem item) {
110 searchView.setOnQueryTextListener(queryListener);
111 return true;
112 }
113
114 @Override
115 public boolean onMenuItemActionCollapse(MenuItem item) {
116 searchView.setOnQueryTextListener(null);
117 viewModel.onSearchClosed();
118 return true;
119 }
120 });
121
122 return true;
123 }
124
125 @Override
126 public boolean onOptionsItemSelected(MenuItem item) {
127 super.onOptionsItemSelected(item);
128
129 if (item.getItemId() == android.R.id.home) {
130 finish();
131 return true;
132 } else if (item.getItemId() == R.id.menu_edit_log) {
133 viewModel.onEditButtonPressed();
134 } else if (item.getItemId() == R.id.menu_done_editing_log) {
135 viewModel.onDoneEditingButtonPressed();
136 } else if (item.getItemId() == R.id.menu_save) {
137 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
138 intent.addCategory(Intent.CATEGORY_OPENABLE);
139 intent.setType("application/zip");
140 intent.putExtra(Intent.EXTRA_TITLE, "signal-log-" + System.currentTimeMillis() + ".zip");
141
142 startActivityForResult(intent, CODE_SAVE);
143 }
144
145 return false;
146 }
147
148 @Override
149 public void onBackPressed() {
150 if (!viewModel.onBackPressed()) {
151 super.onBackPressed();
152 }
153 }
154
155 @Override
156 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
157 super.onActivityResult(requestCode, resultCode, data);
158
159 if (requestCode == CODE_SAVE && resultCode == Activity.RESULT_OK) {
160 Uri uri = data != null ? data.getData() : null;
161 viewModel.onDiskSaveLocationReady(uri);
162 if (progressCard != null) {
163 progressCard.setVisibility(View.VISIBLE);
164 }
165 }
166 }
167
168 @Override
169 public void onLogDeleted(@NonNull LogLine logLine) {
170 viewModel.onLogDeleted(logLine);
171 }
172
173 private void initView() {
174 this.lineList = findViewById(R.id.debug_log_lines);
175 this.warningBanner = findViewById(R.id.debug_log_warning_banner);
176 this.editBanner = findViewById(R.id.debug_log_edit_banner);
177 this.submitButton = findViewById(R.id.debug_log_submit_button);
178 this.scrollToBottomButton = findViewById(R.id.debug_log_scroll_to_bottom);
179 this.scrollToTopButton = findViewById(R.id.debug_log_scroll_to_top);
180 this.progressCard = findViewById(R.id.debug_log_progress_card);
181
182 this.adapter = new SubmitDebugLogAdapter(this, viewModel.getPagingController());
183
184 this.lineList.setLayoutManager(new LinearLayoutManager(this));
185 this.lineList.setAdapter(adapter);
186 this.lineList.setItemAnimator(null);
187
188 submitButton.setOnClickListener(v -> onSubmitClicked());
189
190 scrollToBottomButton.setOnClickListener(v -> lineList.scrollToPosition(adapter.getItemCount() - 1));
191 scrollToTopButton.setOnClickListener(v -> lineList.scrollToPosition(0));
192
193 lineList.addOnScrollListener(new RecyclerView.OnScrollListener() {
194 @Override
195 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
196 if (((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition() < adapter.getItemCount() - 10) {
197 scrollToBottomButton.setVisibility(View.VISIBLE);
198 } else {
199 scrollToBottomButton.setVisibility(View.GONE);
200 }
201
202 if (((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition() > 10) {
203 scrollToTopButton.setVisibility(View.VISIBLE);
204 } else {
205 scrollToTopButton.setVisibility(View.GONE);
206 }
207 }
208 });
209 this.progressCard.setVisibility(View.VISIBLE);
210
211 }
212
213 private void initViewModel() {
214 viewModel.getLines().observe(this, this::presentLines);
215 viewModel.getMode().observe(this, this::presentMode);
216 viewModel.getEvents().observe(this, this::presentEvents);
217 }
218
219 private void presentLines(@NonNull List<LogLine> lines) {
220 if (progressCard != null && lines.size() > 0) {
221 progressCard.setVisibility(View.GONE);
222
223 warningBanner.setVisibility(View.VISIBLE);
224 submitButton.setVisibility(View.VISIBLE);
225 }
226
227 adapter.submitList(lines);
228 }
229
230 private void presentMode(@NonNull SubmitDebugLogViewModel.Mode mode) {
231 switch (mode) {
232 case NORMAL:
233 editBanner.setVisibility(View.GONE);
234 adapter.setEditing(false);
235 saveMenuItem.setVisible(true);
236 // TODO [greyson][log] Not yet implemented
237// editMenuItem.setVisible(true);
238// doneMenuItem.setVisible(false);
239// searchMenuItem.setVisible(true);
240 break;
241 case SUBMITTING:
242 editBanner.setVisibility(View.GONE);
243 adapter.setEditing(false);
244 editMenuItem.setVisible(false);
245 doneMenuItem.setVisible(false);
246 searchMenuItem.setVisible(false);
247 saveMenuItem.setVisible(false);
248 break;
249 case EDIT:
250 editBanner.setVisibility(View.VISIBLE);
251 adapter.setEditing(true);
252 editMenuItem.setVisible(false);
253 doneMenuItem.setVisible(true);
254 searchMenuItem.setVisible(true);
255 saveMenuItem.setVisible(false);
256 break;
257 }
258 }
259
260 private void presentEvents(@NonNull SubmitDebugLogViewModel.Event event) {
261 switch (event) {
262 case FILE_SAVE_SUCCESS:
263 Toast.makeText(this, R.string.SubmitDebugLogActivity_save_complete, Toast.LENGTH_SHORT).show();
264 if (progressCard != null) {
265 progressCard.setVisibility(View.GONE);
266 }
267 break;
268 case FILE_SAVE_ERROR:
269 Toast.makeText(this, R.string.SubmitDebugLogActivity_failed_to_save, Toast.LENGTH_SHORT).show();
270 break;
271 }
272 }
273
274 private void presentResultDialog(@NonNull String url) {
275 AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this)
276 .setTitle(R.string.SubmitDebugLogActivity_success)
277 .setCancelable(false)
278 .setNeutralButton(android.R.string.ok, (d, w) -> finish())
279 .setPositiveButton(R.string.SubmitDebugLogActivity_share, (d, w) -> {
280 ShareCompat.IntentBuilder.from(this)
281 .setText(url)
282 .setType("text/plain")
283 .setEmailTo(new String[] { "support@signal.org" })
284 .startChooser();
285 });
286
287 String dialogText = getResources().getString(R.string.SubmitDebugLogActivity_copy_this_url_and_add_it_to_your_issue, url);
288 SpannableString spannableDialogText = new SpannableString(dialogText);
289 TextView dialogView = new TextView(builder.getContext());
290 LongClickCopySpan longClickUrl = new LongClickCopySpan(url);
291
292
293 LinkifyCompat.addLinks(spannableDialogText, Linkify.WEB_URLS);
294
295 URLSpan[] spans = spannableDialogText.getSpans(0, spannableDialogText.length(), URLSpan.class);
296 for (URLSpan span : spans) {
297 int start = spannableDialogText.getSpanStart(span);
298 int end = spannableDialogText.getSpanEnd(span);
299
300 spannableDialogText.setSpan(longClickUrl, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
301 }
302
303 dialogView.setText(spannableDialogText);
304 dialogView.setMovementMethod(LongClickMovementMethod.getInstance(this));
305
306 ViewUtil.setPadding(dialogView, (int) ThemeUtil.getThemedDimen(this, R.attr.dialogPreferredPadding));
307
308 builder.setView(dialogView);
309 builder.show();
310 }
311
312 private void onSubmitClicked() {
313 submitButton.setSpinning();
314
315 viewModel.onSubmitClicked().observe(this, result -> {
316 if (result.isPresent()) {
317 presentResultDialog(result.get());
318 } else {
319 Toast.makeText(this, R.string.SubmitDebugLogActivity_failed_to_submit_logs, Toast.LENGTH_LONG).show();
320 }
321
322 submitButton.cancelSpinning();
323 });
324 }
325}