/* Copyright 2017 Thomas Schneider * * This file is a part of Fedilab * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ package app.fedilab.android.activities; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.text.Editable; import android.text.Html; import android.text.InputType; import android.text.SpannableString; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; import android.util.Patterns; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import com.smarteist.autoimageslider.IndicatorAnimations; import com.smarteist.autoimageslider.SliderAnimations; import com.smarteist.autoimageslider.SliderView; import com.watermark.androidwm_light.WatermarkBuilder; import com.watermark.androidwm_light.bean.WatermarkText; import net.gotev.uploadservice.MultipartUploadRequest; import net.gotev.uploadservice.ServerResponse; import net.gotev.uploadservice.UploadInfo; import net.gotev.uploadservice.UploadNotificationConfig; import net.gotev.uploadservice.UploadServiceSingleBroadcastReceiver; import net.gotev.uploadservice.UploadStatusDelegate; import org.apache.poi.util.IOUtils; import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.asynctasks.PostStatusAsyncTask; import app.fedilab.android.asynctasks.RetrieveEmojiAsyncTask; import app.fedilab.android.asynctasks.RetrieveSearchAccountsAsyncTask; import app.fedilab.android.asynctasks.RetrieveSearchAsyncTask; import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; import app.fedilab.android.client.Entities.Attachment; import app.fedilab.android.client.Entities.Emojis; import app.fedilab.android.client.Entities.Error; import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; import app.fedilab.android.client.Entities.StoredStatus; import app.fedilab.android.drawers.AccountsSearchAdapter; import app.fedilab.android.drawers.EmojisSearchAdapter; import app.fedilab.android.drawers.SliderAdapter; import app.fedilab.android.drawers.TagsSearchAdapter; import app.fedilab.android.helper.FileNameCleaner; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastalabAutoCompleteTextView; import app.fedilab.android.interfaces.OnDownloadInterface; import app.fedilab.android.interfaces.OnPostActionInterface; import app.fedilab.android.interfaces.OnPostStatusActionInterface; import app.fedilab.android.interfaces.OnRetrieveAttachmentInterface; import app.fedilab.android.interfaces.OnRetrieveEmojiInterface; import app.fedilab.android.interfaces.OnRetrieveSearcAccountshInterface; import app.fedilab.android.interfaces.OnRetrieveSearchInterface; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.StatusStoredDAO; import es.dmoral.toasty.Toasty; import static app.fedilab.android.helper.Helper.THEME_LIGHT; import static app.fedilab.android.helper.Helper.countWithEmoji; /** * Created by Thomas on 01/05/2017. * Toot activity class */ public abstract class BasePixelfedComposeActivity extends BaseActivity implements UploadStatusDelegate, OnPostActionInterface, OnRetrieveSearcAccountshInterface, OnPostStatusActionInterface, OnRetrieveSearchInterface, OnRetrieveEmojiInterface, OnDownloadInterface, OnRetrieveAttachmentInterface { public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 754; public static final int SEND_VOICE_MESSAGE = 1423; private static final int searchDeep = 15; public static HashMap filesMap; public static boolean autocomplete; public static Uri photoFileUri = null; static boolean active = false; private final int PICK_IMAGE = 56556; private final int TAKE_PHOTO = 56532; public long currentToId; protected MastalabAutoCompleteTextView toot_content; protected LinearLayout drawer_layout; String mCurrentPhotoPath; File photoFile = null; private String visibility; private ArrayList attachments; private boolean isSensitive = false; private ImageButton toot_visibility; private Button toot_it; private CheckBox toot_sensitive; private long restored; private TextView title; private ImageView pp_actionBar; private ProgressBar pp_progress; private Toast mToast; private TextView toot_space_left; private String initialContent; private Account accountReply; private Account account; private boolean removed; private int style; private int max_media_count; private UploadServiceSingleBroadcastReceiver uploadReceiver; private UpdateAccountInfoAsyncTask.SOCIAL social; private Button upload_media; private LinearLayout pickup_picture; private SliderView imageSlider; private SliderAdapter sliderAdapter; private CheckBox pixelfed_story; private final BroadcastReceiver imageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String imgpath = intent.getStringExtra("imgpath"); if (imgpath != null) { prepareUpload(BasePixelfedComposeActivity.this, Uri.fromFile(new File(imgpath)), null, uploadReceiver); } } }; private final BroadcastReceiver add_new_media = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { pixelfed_story.setEnabled(true); JSONObject response = null; ArrayList successfullyUploadedFiles; try { response = new JSONObject(Objects.requireNonNull(intent.getStringExtra("response"))); } catch (JSONException e) { e.printStackTrace(); } successfullyUploadedFiles = intent.getStringArrayListExtra("uploadInfo"); addNewMedia(response, successfullyUploadedFiles); } }; public static TextWatcher initializeTextWatcher(Context context, UpdateAccountInfoAsyncTask.SOCIAL social, MastalabAutoCompleteTextView toot_content, TextView toot_space_left, ImageView pp_actionBar, ProgressBar pp_progress, OnRetrieveSearchInterface listener, OnRetrieveSearcAccountshInterface listenerAccount, OnRetrieveEmojiInterface listenerEmoji ) { String pattern = "(.|\\s)*(@[\\w_-]+@[a-z0-9.\\-]+|@[\\w_-]+)"; final Pattern sPattern = Pattern.compile(pattern); String patternTag = "^(.|\\s)*(#([\\w-]{2,}))$"; final Pattern tPattern = Pattern.compile(patternTag); String patternEmoji = "^(.|\\s)*(:([\\w_]+))$"; final Pattern ePattern = Pattern.compile(patternEmoji); final int[] currentCursorPosition = {toot_content.getSelectionStart()}; final int[] searchLength = {searchDeep}; TextWatcher textw; textw = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (toot_content.getSelectionStart() != 0) currentCursorPosition[0] = toot_content.getSelectionStart(); if (s.toString().length() == 0) currentCursorPosition[0] = 0; //Only check last 15 characters before cursor position to avoid lags //Less than 15 characters are written before the cursor position searchLength[0] = Math.min(currentCursorPosition[0], searchDeep); int totalChar = countLength(social, toot_content); toot_space_left.setText(String.valueOf(totalChar)); if (currentCursorPosition[0] - (searchLength[0] - 1) < 0 || currentCursorPosition[0] == 0 || currentCursorPosition[0] > s.toString().length()) return; String[] searchInArray = (s.toString().substring(currentCursorPosition[0] - searchLength[0], currentCursorPosition[0])).split("\\s"); if (searchInArray.length < 1) { return; } String searchIn = searchInArray[searchInArray.length - 1]; Matcher m, mt; m = sPattern.matcher(searchIn); if (m.matches()) { String search = m.group(); if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.VISIBLE); pp_actionBar.setVisibility(View.GONE); } new RetrieveSearchAccountsAsyncTask(context, search, listenerAccount); } else { mt = tPattern.matcher(searchIn); if (mt.matches()) { String search = mt.group(3); if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.VISIBLE); pp_actionBar.setVisibility(View.GONE); } new RetrieveSearchAsyncTask(context, search, true, listener); } else { mt = ePattern.matcher(searchIn); if (mt.matches()) { String shortcode = mt.group(3); if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.VISIBLE); pp_actionBar.setVisibility(View.GONE); } new RetrieveEmojiAsyncTask(context, shortcode, listenerEmoji); } else { toot_content.dismissDropDown(); } } } totalChar = countLength(social, toot_content); toot_space_left.setText(String.valueOf(totalChar)); } }; return textw; } static private void upload(Activity activity, boolean pixelfedStory, Account account, Uri inUri, String fname, UploadServiceSingleBroadcastReceiver uploadReceiver) { String uploadId = UUID.randomUUID().toString(); if (uploadReceiver != null) { uploadReceiver.setUploadID(uploadId); } InputStream tempInput = null; SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); boolean isWM = sharedpreferences.getBoolean(Helper.SET_WATERMARK, false); ContentResolver cr = activity.getContentResolver(); String mime = cr.getType(inUri); if ((mime == null || mime.toLowerCase().contains("image")) && isWM) { String WMText = sharedpreferences.getString(Helper.SET_WATERMARK_TEXT + account.getId() + account.getInstance(), null); if (WMText == null || WMText.trim().length() == 0) { WMText = "@" + account.getUsername() + "@" + account.getInstance(); } float textSize = 15; Paint mPaint = new Paint(); mPaint.setTextSize(textSize); float width = mPaint.measureText(WMText, 0, WMText.length()); try { Bitmap backgroundBitmap = MediaStore.Images.Media.getBitmap(activity.getContentResolver(), inUri); int w = backgroundBitmap.getWidth(); int h = backgroundBitmap.getHeight(); float valx = (float) 1.0 - ((Helper.convertDpToPixel(width, activity) + 10)) / (float) w; if (valx < 0) valx = 0; float valy = (h - Helper.convertDpToPixel(textSize, activity) - 10) / (float) h; WatermarkText watermarkText = new WatermarkText(WMText) .setPositionX(valx) .setPositionY(valy) .setTextColor(Color.WHITE) .setTextShadow(0.1f, 1, 1, Color.LTGRAY) .setTextAlpha(200) .setRotation(0) .setTextSize(textSize); Bitmap bitmap = WatermarkBuilder .create(activity, backgroundBitmap) .loadWatermarkText(watermarkText) .getWatermark() .getOutputImage(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos); byte[] bitmapdata = bos.toByteArray(); tempInput = new ByteArrayInputStream(bitmapdata); } catch (Exception e) { e.printStackTrace(); } } Uri uri; FileOutputStream tempOut = null; String filename = inUri.toString().substring(inUri.toString().lastIndexOf("/")); int suffixPosition = filename.lastIndexOf("."); String suffix = ""; if (suffixPosition > 0) suffix = filename.substring(suffixPosition); try { File file; if (tempInput == null) { tempInput = activity.getContentResolver().openInputStream(inUri); } if (fname.startsWith("fedilabins_")) { file = File.createTempFile("fedilabins_randomTemp1", suffix, activity.getCacheDir()); } else { file = File.createTempFile("randomTemp1", suffix, activity.getCacheDir()); } filesMap.put(file.getAbsolutePath(), inUri); tempOut = new FileOutputStream(file.getAbsoluteFile()); byte[] buff = new byte[1024]; int read; assert tempInput != null; while ((read = tempInput.read(buff)) > 0) { tempOut.write(buff, 0, read); } if (BuildConfig.DONATIONS) { uri = FileProvider.getUriForFile(activity, "fr.gouv.etalab.mastodon.fileProvider", file); } else { if (BuildConfig.lite) { uri = FileProvider.getUriForFile(activity, "app.fedilab.lite.fileProvider", file); } else { uri = FileProvider.getUriForFile(activity, "app.fedilab.android.fileProvider", file); } } tempInput.close(); tempOut.close(); } catch (IOException e) { e.printStackTrace(); uri = inUri; } finally { IOUtils.closeQuietly(tempInput); IOUtils.closeQuietly(tempOut); } try { final String fileName = FileNameCleaner.cleanFileName(fname); String scheme = sharedpreferences.getString(Helper.SET_ONION_SCHEME + Helper.getLiveInstance(activity), "https"); String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); int maxUploadRetryTimes = sharedpreferences.getInt(Helper.MAX_UPLOAD_IMG_RETRY_TIMES, 3); String url = scheme + "://" + Helper.getLiveInstance(activity) + "/api/compose/v0/media/upload"; if (pixelfedStory) { url = scheme + "://" + Helper.getLiveInstance(activity) + "/api/stories/v0/add"; } UploadNotificationConfig uploadConfig = new UploadNotificationConfig(); uploadConfig .setClearOnActionForAllStatuses(true); uploadConfig.getProgress().message = activity.getString(R.string.uploading); uploadConfig.getCompleted().autoClear = true; MultipartUploadRequest request = new MultipartUploadRequest(activity, uploadId, url); String cookie; if (token != null) { cookie = token.split("\\|")[1]; request.addHeader("cookie", cookie); String[] tokens = token.split("\\|")[0].split(";"); request.addHeader("x-xsrf-token", tokens[0].replace("X-XSRF-TOKEN= ", "")); request.addHeader("x-csrf-token", tokens[1].replace("X-CSRF-TOKEN= ", "")); request.setNotificationConfig(uploadConfig); request.addFileToUpload(uri.toString().replace("file://", ""), "file"); request.addParameter("filename", fileName).setMaxRetries(maxUploadRetryTimes); request.startUpload(); } } catch (MalformedURLException | FileNotFoundException e) { e.printStackTrace(); } } public static int countLength(UpdateAccountInfoAsyncTask.SOCIAL social, MastalabAutoCompleteTextView toot_content) { if (toot_content == null) { return -1; } String content = toot_content.getText().toString(); String contentCount = content; if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON) { contentCount = contentCount.replaceAll("(?i)(^|[^/\\w])@(([a-z0-9_]+)@[a-z0-9.\\-]+[a-z0-9]+)", "$1@$3"); Matcher matcherALink = Patterns.WEB_URL.matcher(contentCount); while (matcherALink.find()) { final String url = matcherALink.group(1); assert url != null; contentCount = contentCount.replace(url, "abcdefghijklmnopkrstuvw"); } } return contentCount.length() - countWithEmoji(content); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); final int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); switch (theme) { case Helper.THEME_LIGHT: setTheme(R.style.AppTheme_Fedilab); break; case Helper.THEME_BLACK: setTheme(R.style.AppThemeBlack); break; default: setTheme(R.style.AppThemeDark); } if (theme == Helper.THEME_DARK) { style = R.style.DialogDark; } else if (theme == Helper.THEME_BLACK) { style = R.style.DialogBlack; } else { style = R.style.Dialog; } filesMap = new HashMap<>(); social = MainActivity.social; autocomplete = false; setContentView(R.layout.activity_pixelfed_compose); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); assert inflater != null; View view = inflater.inflate(R.layout.toot_action_bar, new LinearLayout(BasePixelfedComposeActivity.this), false); view.setBackground(new ColorDrawable(ContextCompat.getColor(BasePixelfedComposeActivity.this, R.color.cyanea_primary))); actionBar.setCustomView(view, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); ImageView close_toot = actionBar.getCustomView().findViewById(R.id.close_toot); close_toot.setOnClickListener(v -> { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); assert inputMethodManager != null; inputMethodManager.hideSoftInputFromWindow(toot_content.getWindowToken(), 0); boolean storeToot = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); if (!storeToot) { if (toot_content.getText().toString().trim().length() == 0 && (attachments == null || attachments.size() < 1)) { finish(); } else if (initialContent.trim().equals(toot_content.getText().toString().trim())) { finish(); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(BasePixelfedComposeActivity.this, style); dialogBuilder.setMessage(R.string.save_draft); dialogBuilder.setPositiveButton(R.string.save, (dialog, id) -> { storeToot(accountReply == null, false); dialog.dismiss(); finish(); }); dialogBuilder.setNegativeButton(R.string.discard, (dialog, id) -> { dialog.dismiss(); finish(); }); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.setCancelable(false); alertDialog.show(); } } else { finish(); } }); title = actionBar.getCustomView().findViewById(R.id.toolbar_title); pp_actionBar = actionBar.getCustomView().findViewById(R.id.pp_actionBar); pp_progress = actionBar.getCustomView().findViewById(R.id.pp_progress); } //By default the toot is not restored so the id -1 is defined currentToId = -1; toot_it = findViewById(R.id.toot_it); attachments = new ArrayList<>(); imageSlider = findViewById(R.id.imageSlider); sliderAdapter = new SliderAdapter(new WeakReference<>(BasePixelfedComposeActivity.this), true, attachments); imageSlider.setIndicatorAnimation(IndicatorAnimations.WORM); imageSlider.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION); imageSlider.setSliderAdapter(sliderAdapter); upload_media = findViewById(R.id.upload_media); toot_space_left = findViewById(R.id.toot_space_left); toot_visibility = findViewById(R.id.toot_visibility); pickup_picture = findViewById(R.id.pickup_picture); toot_content = findViewById(R.id.toot_content); int newInputType = toot_content.getInputType() & (toot_content.getInputType() ^ InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); toot_content.setInputType(newInputType); ScrollView composer_container = findViewById(R.id.composer_container); pixelfed_story = findViewById(R.id.pixelfed_story); //There is no media the button is hidden upload_media.setVisibility(View.INVISIBLE); toot_sensitive = findViewById(R.id.toot_sensitive); drawer_layout = findViewById(R.id.drawer_layout); ImageButton toot_emoji = findViewById(R.id.toot_emoji); LinearLayout bottom_bar_tooting = findViewById(R.id.bottom_bar_tooting); if (sharedpreferences.getBoolean(Helper.SET_DISPLAY_EMOJI, false)) { displayEmojiPopup(); } else { toot_emoji.setVisibility(View.GONE); } TextView indication_story = findViewById(R.id.indication_story); pixelfed_story.setOnCheckedChangeListener((buttonView, isChecked) -> { if (isChecked) { composer_container.setVisibility(View.GONE); indication_story.setVisibility(View.VISIBLE); bottom_bar_tooting.setVisibility(View.GONE); } else { composer_container.setVisibility(View.VISIBLE); indication_story.setVisibility(View.GONE); bottom_bar_tooting.setVisibility(View.VISIBLE); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(BasePixelfedComposeActivity.this); int iconColor = prefs.getInt("theme_icons_color", -1); if (iconColor != -1) { Helper.changeDrawableColor(BasePixelfedComposeActivity.this, toot_visibility, iconColor); Helper.changeDrawableColor(BasePixelfedComposeActivity.this, toot_emoji, iconColor); toot_sensitive.setButtonTintList(ColorStateList.valueOf(iconColor)); toot_sensitive.setTextColor(iconColor); } } Bundle b = getIntent().getExtras(); ArrayList sharedUri = new ArrayList<>(); SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); restored = -1; if (b != null) { String accountReplyToken = b.getString("accountReplyToken", null); accountReply = null; if (accountReplyToken != null) { String[] val = accountReplyToken.split("\\|"); if (val.length == 2) { accountReply = new AccountDAO(BasePixelfedComposeActivity.this, db).getUniqAccount(val[0], val[1]); } } removed = b.getBoolean("removed"); visibility = b.getString("visibility", null); // ACTION_SEND route if (b.getInt("uriNumberMast", 0) == 1) { Uri fileUri = b.getParcelable("sharedUri"); if (fileUri != null) { sharedUri.add(fileUri); } } // ACTION_SEND_MULTIPLE route else if (b.getInt("uriNumberMast", 0) > 1) { ArrayList fileUri = b.getParcelableArrayList("sharedUri"); if (fileUri != null) { sharedUri.addAll(fileUri); } } restored = b.getLong("restored", -1); } if (!sharedUri.isEmpty()) { uploadSharedImage(sharedUri); } String userIdReply, instanceReply; if (accountReply == null) { userIdReply = sharedpreferences.getString(Helper.PREF_KEY_ID, null); instanceReply = sharedpreferences.getString(Helper.PREF_INSTANCE, null); } else { userIdReply = accountReply.getId(); instanceReply = accountReply.getInstance(); } if (accountReply == null) account = new AccountDAO(BasePixelfedComposeActivity.this, db).getUniqAccount(userIdReply, instanceReply); else account = accountReply; max_media_count = 4; setTitle(R.string.compose); toot_content.requestFocus(); Helper.loadGiF(BasePixelfedComposeActivity.this, account, pp_actionBar); if (visibility == null) { String defaultVisibility = account.isLocked() ? "private" : "public"; visibility = sharedpreferences.getString(Helper.SET_TOOT_VISIBILITY + "@" + account.getAcct() + "@" + account.getInstance(), defaultVisibility); } switch (visibility) { case "public": toot_visibility.setImageResource(R.drawable.ic_public_toot); break; case "unlisted": toot_visibility.setImageResource(R.drawable.ic_lock_open_toot); break; case "private": toot_visibility.setImageResource(R.drawable.ic_lock_outline_toot); break; case "direct": toot_visibility.setImageResource(R.drawable.ic_mail_outline_toot); break; } toot_sensitive.setOnCheckedChangeListener((buttonView, isChecked) -> isSensitive = isChecked); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); toot_visibility.setOnClickListener(v -> tootVisibilityDialog()); toot_it.setOnClickListener(v -> sendToot()); pickup_picture.setOnClickListener(v -> { if (ContextCompat.checkSelfPermission(BasePixelfedComposeActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(BasePixelfedComposeActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); return; } Intent intent; intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); String[] mimetypes = {"image/*", "video/*", "audio/mpeg", "audio/opus", "audio/flac", "audio/wav", "audio/ogg"}; intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); startActivityForResult(intent, PICK_IMAGE); }); upload_media.setOnClickListener(v -> { if (ContextCompat.checkSelfPermission(BasePixelfedComposeActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(BasePixelfedComposeActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); return; } Intent intent; intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); String[] mimetypes = {"image/*", "video/*", "audio/mpeg", "audio/opus", "audio/flac", "audio/wav", "audio/ogg"}; intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); startActivityForResult(intent, PICK_IMAGE); }); TextWatcher textWatcher = initializeTextWatcher(BasePixelfedComposeActivity.this, social, toot_content, toot_space_left, pp_actionBar, pp_progress, BasePixelfedComposeActivity.this, BasePixelfedComposeActivity.this, BasePixelfedComposeActivity.this); toot_content.addTextChangedListener(textWatcher); if (restored != -1) { restoreToot(restored); } toot_space_left.setText(String.valueOf(countLength(social, toot_content))); LocalBroadcastManager.getInstance(this) .registerReceiver(imageReceiver, new IntentFilter(Helper.INTENT_SEND_MODIFIED_IMAGE)); uploadReceiver = new UploadServiceSingleBroadcastReceiver(this); uploadReceiver.register(this); LocalBroadcastManager.getInstance(this) .registerReceiver(add_new_media, new IntentFilter(Helper.INTENT_ADD_UPLOADED_MEDIA)); } private void addNewMedia(JSONObject response, ArrayList successfullyUploadedFiles) { if (!pixelfed_story.isChecked()) { Attachment attachment; attachment = API.parseAttachmentResponse(response); boolean alreadyAdded = false; int index = 0; for (Attachment attach_ : attachments) { if (attach_.getId().equals(attachment.getId())) { alreadyAdded = true; break; } index++; } if (attachment.getPreview_url().contains("no-preview.png") && successfullyUploadedFiles != null && successfullyUploadedFiles.size() > 0) { attachment.setPreview_url(successfullyUploadedFiles.get(0)); } if (!alreadyAdded) { attachments.add(attachment); sliderAdapter.notifyDataSetChanged(); imageSlider.setVisibility(View.VISIBLE); pickup_picture.setVisibility(View.GONE); upload_media.setVisibility(View.VISIBLE); if (attachments.size() < max_media_count) upload_media.setEnabled(true); toot_it.setEnabled(true); toot_sensitive.setVisibility(View.VISIBLE); if (account.isSensitive()) { toot_sensitive.setChecked(true); } imageSlider.setCurrentPagePosition(imageSlider.getChildCount()); } else { if (attachments.size() > index && attachment.getDescription() != null) { attachments.get(index).setDescription(attachment.getDescription()); } } } else { Toasty.success(BasePixelfedComposeActivity.this, getString(R.string.added_to_story), Toast.LENGTH_LONG).show(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {// If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // We have the permission. upload_media.callOnClick(); } } } public void showAToast(String message) { if (mToast != null) { mToast.cancel(); } mToast = Toasty.error(BasePixelfedComposeActivity.this, message, Toast.LENGTH_SHORT); mToast.show(); } // Handles uploading shared images public void uploadSharedImage(ArrayList uri) { if (!uri.isEmpty()) { int count = 0; for (Uri fileUri : uri) { if (fileUri != null) { if (count == max_media_count) { break; } try { prepareUpload(BasePixelfedComposeActivity.this, fileUri, null, uploadReceiver); count++; } catch (Exception e) { e.printStackTrace(); Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); upload_media.setEnabled(true); toot_it.setEnabled(true); } } else { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); } } } } private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // Ensure that there's a camera activity to handle the intent if (takePictureIntent.resolveActivity(getPackageManager()) != null) { // Create the File where the photo should go try { photoFile = createImageFile(); } catch (IOException ignored) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); } // Continue only if the File was successfully created if (photoFile != null) { if (BuildConfig.DONATIONS) { photoFileUri = FileProvider.getUriForFile(this, "fr.gouv.etalab.mastodon.fileProvider", photoFile); } else { if (BuildConfig.lite) { photoFileUri = FileProvider.getUriForFile(this, "app.fedilab.lite.fileProvider", photoFile); } else { photoFileUri = FileProvider.getUriForFile(this, "app.fedilab.android.fileProvider", photoFile); } } takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoFileUri); startActivityForResult(takePictureIntent, TAKE_PHOTO); } } } private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents mCurrentPhotoPath = image.getAbsolutePath(); return image; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); boolean photo_editor = sharedpreferences.getBoolean(Helper.SET_PHOTO_EDITOR, true); if (requestCode == PICK_IMAGE && resultCode == RESULT_OK) { if (data == null) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); return; } ClipData clipData = data.getClipData(); if (data.getData() == null && clipData == null) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); return; } if (clipData != null) { ArrayList mArrayUri = new ArrayList<>(); for (int i = 0; i < clipData.getItemCount(); i++) { ClipData.Item item = clipData.getItemAt(i); Uri uri = item.getUri(); mArrayUri.add(uri); } uploadSharedImage(mArrayUri); } else { String filename = Helper.getFileName(BasePixelfedComposeActivity.this, data.getData()); ContentResolver cr = getContentResolver(); String mime = cr.getType(data.getData()); if (mime != null && (mime.toLowerCase().contains("video") || mime.toLowerCase().contains("gif"))) { prepareUpload(BasePixelfedComposeActivity.this, data.getData(), filename, uploadReceiver); } else if (mime != null && mime.toLowerCase().contains("image")) { if (photo_editor) { launchPhotoEditor(data.getData()); } else { prepareUpload(BasePixelfedComposeActivity.this, data.getData(), filename, uploadReceiver); } } else if (mime != null && mime.toLowerCase().contains("audio")) { prepareUpload(BasePixelfedComposeActivity.this, data.getData(), filename, uploadReceiver); } else { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); } } } else if (requestCode == SEND_VOICE_MESSAGE && resultCode == RESULT_OK) { Uri uri = Uri.fromFile(new File(getCacheDir() + "/fedilab_recorded_audio.wav")); prepareUpload(BasePixelfedComposeActivity.this, uri, "fedilab_recorded_audio.wav", uploadReceiver); } else if (requestCode == TAKE_PHOTO && resultCode == RESULT_OK) { if (photo_editor) { launchPhotoEditor(photoFileUri); } else { prepareUpload(BasePixelfedComposeActivity.this, photoFileUri, null, uploadReceiver); } } } private void prepareUpload(Activity activity, Uri uri, String filename, UploadServiceSingleBroadcastReceiver uploadReceiver) { pixelfed_story.setEnabled(false); if (uploadReceiver == null) { uploadReceiver = new UploadServiceSingleBroadcastReceiver(BasePixelfedComposeActivity.this); uploadReceiver.register(this); } new asyncPicture(activity, pixelfed_story.isChecked(), account, uri, filename, uploadReceiver); } @Override public void onPostAction(int statusCode, API.StatusAction statusAction, String userId, Error error) { if (error != null) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } else { Toasty.success(BasePixelfedComposeActivity.this, getString(R.string.toot_sent), Toast.LENGTH_LONG).show(); resetForNextToot(); } } @Override public void onProgress(Context context, UploadInfo uploadInfo) { // your code here } @Override public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse, Exception exception) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); if (attachments.size() == 0) { pickup_picture.setVisibility(View.VISIBLE); imageSlider.setVisibility(View.GONE); } upload_media.setEnabled(true); toot_it.setEnabled(true); pixelfed_story.setEnabled(true); } @Override public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) { JSONObject response; pixelfed_story.setEnabled(true); try { response = new JSONObject(serverResponse.getBodyAsString()); addNewMedia(response, uploadInfo.getSuccessfullyUploadedFiles()); } catch (JSONException e) { e.printStackTrace(); } } @Override public void onCancelled(Context context, UploadInfo uploadInfo) { // your code here pixelfed_story.setEnabled(true); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Bundle extras = intent.getExtras(); if (extras != null && extras.getString("imageUri") != null) { Uri imageUri = Uri.parse(extras.getString("imageUri")); if (imageUri == null) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_select_image_error), Toast.LENGTH_LONG).show(); return; } String filename = Helper.getFileName(BasePixelfedComposeActivity.this, imageUri); prepareUpload(BasePixelfedComposeActivity.this, imageUri, filename, uploadReceiver); } } @Override public boolean onOptionsItemSelected(@NotNull MenuItem item) { int itemId = item.getItemId(); if (itemId == android.R.id.home) { finish(); return true; } else if (itemId == R.id.action_photo_camera) { dispatchTakePictureIntent(); return true; } return super.onOptionsItemSelected(item); } private void sendToot() { toot_it.setEnabled(false); if (toot_content.getText().toString().trim().length() == 0 && attachments.size() == 0) { Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toot_error_no_content), Toast.LENGTH_LONG).show(); toot_it.setEnabled(true); return; } String tootContent = toot_content.getText().toString().trim(); Status toot = new Status(); toot.setSensitive(isSensitive); toot.setVisibility(visibility); toot.setMedia_attachments(attachments); toot.setContent(BasePixelfedComposeActivity.this, tootContent); new PostStatusAsyncTask(BasePixelfedComposeActivity.this, social, account, toot, BasePixelfedComposeActivity.this); } private void resetForNextToot() { //Clear content toot_content.setText(""); toot_space_left.setText("0"); if (attachments != null) { for (Attachment attachment : attachments) { View namebar = findViewById(attachment.getViewId()); if (namebar != null && namebar.getParent() != null) ((ViewGroup) namebar.getParent()).removeView(namebar); } List tmp_attachment = new ArrayList<>(attachments); attachments.removeAll(tmp_attachment); tmp_attachment.clear(); } isSensitive = false; toot_sensitive.setVisibility(View.GONE); currentToId = -1; Toasty.info(BasePixelfedComposeActivity.this, getString(R.string.toot_sent), Toast.LENGTH_LONG).show(); } @Override public boolean onCreateOptionsMenu(@NotNull Menu menu) { getMenuInflater().inflate(R.menu.main_compose_pixelfed, menu); SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); if (theme == THEME_LIGHT) { //Hack for colors in compose menu for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); SpannableString s = new SpannableString(item.getTitle()); s.setSpan(new ForegroundColorSpan(Color.BLACK), 0, s.length(), 0); item.setTitle(s); } } return true; } @Override public void onDownloaded(String pathToFile, String url, Error error) { if (error == null && pathToFile != null) { Uri uri = Uri.fromFile(new File(pathToFile)); String filename = FileNameCleaner.cleanFileName(url); upload_media.setEnabled(false); toot_it.setEnabled(false); upload(BasePixelfedComposeActivity.this, pixelfed_story.isChecked(), account, uri, filename, uploadReceiver); } } @Override public void onRetrieveAttachment(Attachment attachment, String fileName, Error error) { } @Override public void onUpdateProgress(int progress) { ProgressBar progressBar = findViewById(R.id.upload_progress); TextView toolbar_text = findViewById(R.id.toolbar_text); RelativeLayout progress_bar_container = findViewById(R.id.progress_bar_container); if (progress <= 100) { progressBar.setScaleY(3f); progress_bar_container.setVisibility(View.VISIBLE); progressBar.setProgress(progress); toolbar_text.setText(String.format("%s%%", progress)); } else { progress_bar_container.setVisibility(View.GONE); } } private void tootVisibilityDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(BasePixelfedComposeActivity.this, style); dialog.setTitle(R.string.toot_visibility_tilte); final String[] stringArray = getResources().getStringArray(R.array.toot_visibility); final ArrayAdapter arrayAdapter = new ArrayAdapter<>(BasePixelfedComposeActivity.this, android.R.layout.simple_list_item_1, stringArray); dialog.setNegativeButton(R.string.cancel, (dialog12, position) -> dialog12.dismiss()); dialog.setAdapter(arrayAdapter, (dialog1, position) -> { switch (position) { case 0: visibility = "public"; toot_visibility.setImageResource(R.drawable.ic_public_toot); break; case 1: visibility = "unlisted"; toot_visibility.setImageResource(R.drawable.ic_lock_open_toot); break; case 2: visibility = "private"; toot_visibility.setImageResource(R.drawable.ic_lock_outline_toot); break; case 3: visibility = "direct"; toot_visibility.setImageResource(R.drawable.ic_mail_outline_toot); break; } dialog1.dismiss(); }); dialog.show(); } @Override protected void onResume() { super.onResume(); } @Override public void onPause() { super.onPause(); final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); boolean storeToot = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); if (storeToot && accountReply == null) { storeToot(true, false); } else if (storeToot) { storeToot(false, false); } } @Override public void onBackPressed() { final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); boolean storeToot = sharedpreferences.getBoolean(Helper.SET_AUTO_STORE, true); if (!storeToot) { if (toot_content.getText().toString().trim().length() == 0 && (attachments == null || attachments.size() < 1)) { finish(); } else if (initialContent.trim().equals(toot_content.getText().toString().trim())) { finish(); } else { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(BasePixelfedComposeActivity.this, style); dialogBuilder.setMessage(R.string.save_draft); dialogBuilder.setPositiveButton(R.string.save, (dialog, id) -> { storeToot(accountReply == null, false); dialog.dismiss(); finish(); }); dialogBuilder.setNegativeButton(R.string.discard, (dialog, id) -> { dialog.dismiss(); finish(); }); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.setCancelable(false); alertDialog.show(); } } else { super.onBackPressed(); } } @Override public void onDestroy() { super.onDestroy(); LocalBroadcastManager.getInstance(this) .unregisterReceiver(imageReceiver); LocalBroadcastManager.getInstance(this) .unregisterReceiver(add_new_media); uploadReceiver.unregister(this); } @Override public void onPostStatusAction(APIResponse apiResponse) { if (apiResponse.getError() != null) { toot_it.setEnabled(true); if (apiResponse.getError().getStatusCode() == -33) { storeToot(false, true); } else { if (apiResponse.getError().getStatusCode() == 404) { storeToot(false, true); } showAToast(apiResponse.getError().getError()); return; } } if (apiResponse.getError() == null || apiResponse.getError().getStatusCode() != -33) { if (restored != -1) { SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); new StatusStoredDAO(BasePixelfedComposeActivity.this, db).remove(restored); } else if (currentToId != -1) { SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); new StatusStoredDAO(BasePixelfedComposeActivity.this, db).remove(currentToId); } } //Clear the toot toot_content.setText(""); toot_space_left.setText("0"); if (attachments != null) { for (Attachment attachment : attachments) { View namebar = findViewById(attachment.getViewId()); if (namebar != null && namebar.getParent() != null) ((ViewGroup) namebar.getParent()).removeView(namebar); } List tmp_attachment = new ArrayList<>(attachments); attachments.removeAll(tmp_attachment); tmp_attachment.clear(); } isSensitive = false; toot_sensitive.setVisibility(View.GONE); currentToId = -1; if (apiResponse.getError() == null) { Toasty.success(BasePixelfedComposeActivity.this, getString(R.string.toot_sent), Toast.LENGTH_LONG).show(); } else { if (apiResponse.getError().getStatusCode() == -33) Toasty.info(BasePixelfedComposeActivity.this, getString(R.string.toast_toot_saved_error), Toast.LENGTH_LONG).show(); } toot_it.setEnabled(true); //It's a reply, so the user will be redirect to its answer Intent intent = new Intent(BasePixelfedComposeActivity.this, MainActivity.class); intent.putExtra(Helper.INTENT_ACTION, Helper.HOME_TIMELINE_INTENT); startActivity(intent); finish(); } @Override public void onRetrieveSearchAccounts(APIResponse apiResponse) { if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.GONE); pp_actionBar.setVisibility(View.VISIBLE); } if (apiResponse.getError() != null) return; final List accounts = apiResponse.getAccounts(); if (accounts != null && accounts.size() > 0) { int currentCursorPosition = toot_content.getSelectionStart(); AccountsSearchAdapter accountsListAdapter = new AccountsSearchAdapter(BasePixelfedComposeActivity.this, accounts); toot_content.setThreshold(1); toot_content.setAdapter(accountsListAdapter); final String oldContent = toot_content.getText().toString(); if (oldContent.length() >= currentCursorPosition) { String[] searchA = oldContent.substring(0, currentCursorPosition).split("@"); if (searchA.length > 0) { final String search = searchA[searchA.length - 1]; toot_content.setOnItemClickListener((parent, view, position, id) -> { Account account = accounts.get(position); String deltaSearch = ""; int searchLength = searchDeep; if (currentCursorPosition < searchDeep) { //Less than 15 characters are written before the cursor position searchLength = currentCursorPosition; } if (currentCursorPosition - searchLength > 0 && currentCursorPosition < oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength, currentCursorPosition); else { if (currentCursorPosition >= oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength); } if (!search.equals("")) deltaSearch = deltaSearch.replace("@" + search, ""); String newContent = oldContent.substring(0, currentCursorPosition - searchLength); newContent += deltaSearch; newContent += "@" + account.getAcct() + " "; int newPosition = newContent.length(); if (currentCursorPosition < oldContent.length()) newContent += oldContent.substring(currentCursorPosition); toot_content.setText(newContent); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); toot_content.setSelection(newPosition); AccountsSearchAdapter accountsListAdapter1 = new AccountsSearchAdapter(BasePixelfedComposeActivity.this, new ArrayList<>()); toot_content.setThreshold(1); toot_content.setAdapter(accountsListAdapter1); }); } } } } @Override public void onRetrieveContact(APIResponse apiResponse) { } @Override public void onRetrieveEmoji(Status status, boolean fromTranslation) { } @Override public void onRetrieveEmoji(Notification notification) { } @Override public void onRetrieveSearchEmoji(final List emojis) { if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.GONE); pp_actionBar.setVisibility(View.VISIBLE); } if (emojis != null && emojis.size() > 0) { int currentCursorPosition = toot_content.getSelectionStart(); EmojisSearchAdapter emojisSearchAdapter = new EmojisSearchAdapter(BasePixelfedComposeActivity.this, emojis); toot_content.setThreshold(1); toot_content.setAdapter(emojisSearchAdapter); final String oldContent = toot_content.getText().toString(); String[] searchA = oldContent.substring(0, currentCursorPosition).split(":"); if (searchA.length > 0) { final String search = searchA[searchA.length - 1]; toot_content.setOnItemClickListener((parent, view, position, id) -> { String shortcode = emojis.get(position).getShortcode(); String deltaSearch = ""; int searchLength = searchDeep; if (currentCursorPosition < searchDeep) { //Less than 15 characters are written before the cursor position searchLength = currentCursorPosition; } if (currentCursorPosition - searchLength > 0 && currentCursorPosition < oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength, currentCursorPosition); else { if (currentCursorPosition >= oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength); } if (!search.equals("")) deltaSearch = deltaSearch.replace(":" + search, ""); String newContent = oldContent.substring(0, currentCursorPosition - searchLength); newContent += deltaSearch; newContent += ":" + shortcode + ": "; int newPosition = newContent.length(); if (currentCursorPosition < oldContent.length()) newContent += oldContent.substring(currentCursorPosition); toot_content.setText(newContent); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); toot_content.setSelection(newPosition); EmojisSearchAdapter emojisSearchAdapter1 = new EmojisSearchAdapter(BasePixelfedComposeActivity.this, new ArrayList<>()); toot_content.setThreshold(1); toot_content.setAdapter(emojisSearchAdapter1); }); } } } @Override public void onRetrieveSearch(APIResponse apiResponse) { if (pp_progress != null && pp_actionBar != null) { pp_progress.setVisibility(View.GONE); pp_actionBar.setVisibility(View.VISIBLE); } if (apiResponse == null || apiResponse.getResults() == null) return; Results results = apiResponse.getResults(); final List tags = results.getHashtags(); if (tags != null && tags.size() > 0) { int currentCursorPosition = toot_content.getSelectionStart(); TagsSearchAdapter tagsSearchAdapter = new TagsSearchAdapter(BasePixelfedComposeActivity.this, tags); toot_content.setThreshold(1); toot_content.setAdapter(tagsSearchAdapter); final String oldContent = toot_content.getText().toString(); if (oldContent.length() < currentCursorPosition) return; String[] searchA = oldContent.substring(0, currentCursorPosition).split("#"); if (searchA.length < 1) return; final String search = searchA[searchA.length - 1]; toot_content.setOnItemClickListener((parent, view, position, id) -> { if (position >= tags.size()) return; String tag = tags.get(position); String deltaSearch = ""; int searchLength = searchDeep; if (currentCursorPosition < searchDeep) { //Less than 15 characters are written before the cursor position searchLength = currentCursorPosition; } if (currentCursorPosition - searchLength > 0 && currentCursorPosition < oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength, currentCursorPosition); else { if (currentCursorPosition >= oldContent.length()) deltaSearch = oldContent.substring(currentCursorPosition - searchLength); } if (!search.equals("")) deltaSearch = deltaSearch.replace("#" + search, ""); String newContent = oldContent.substring(0, currentCursorPosition - searchLength); newContent += deltaSearch; newContent += "#" + tag + " "; int newPosition = newContent.length(); if (currentCursorPosition < oldContent.length()) newContent += oldContent.substring(currentCursorPosition); toot_content.setText(newContent); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); toot_content.setSelection(newPosition); TagsSearchAdapter tagsSearchAdapter1 = new TagsSearchAdapter(BasePixelfedComposeActivity.this, new ArrayList<>()); toot_content.setThreshold(1); toot_content.setAdapter(tagsSearchAdapter1); }); } } private void restoreToot(long id) { SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); StoredStatus draft = new StatusStoredDAO(BasePixelfedComposeActivity.this, db).getStatus(id); if (draft == null) return; Status status = draft.getStatus(); //Retrieves attachments if (removed) { new StatusStoredDAO(BasePixelfedComposeActivity.this, db).remove(draft.getId()); } restored = id; attachments = status.getMedia_attachments(); String content = status.getContent(); Pattern mentionLink = Pattern.compile("(<\\s?a\\s?href=\"https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[/\\w._-]*)\"\\s?[^.]*<\\s?/\\s?a\\s?>)"); Matcher matcher = mentionLink.matcher(content); if (matcher.find()) { content = matcher.replaceAll("$3@$2"); } if (removed) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) content = Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY).toString(); else content = Html.fromHtml(content).toString(); } if (attachments != null && attachments.size() > 0) { sliderAdapter = new SliderAdapter(new WeakReference<>(BasePixelfedComposeActivity.this), true, attachments); imageSlider.setIndicatorAnimation(IndicatorAnimations.WORM); imageSlider.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION); imageSlider.setSliderAdapter(sliderAdapter); imageSlider.setVisibility(View.VISIBLE); pickup_picture.setVisibility(View.GONE); upload_media.setVisibility(View.VISIBLE); if (attachments.size() < max_media_count) upload_media.setEnabled(true); toot_it.setEnabled(true); toot_sensitive.setVisibility(View.VISIBLE); if (account.isSensitive()) { toot_sensitive.setChecked(true); } } else { imageSlider.setVisibility(View.GONE); pickup_picture.setVisibility(View.VISIBLE); } //Sensitive content toot_sensitive.setChecked(status.isSensitive()); toot_content.setText(content); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); toot_content.setSelection(toot_content.getText().length()); switch (status.getVisibility()) { case "public": visibility = "public"; toot_visibility.setImageResource(R.drawable.ic_public_toot); break; case "unlisted": visibility = "unlisted"; toot_visibility.setImageResource(R.drawable.ic_lock_open_toot); break; case "private": visibility = "private"; toot_visibility.setImageResource(R.drawable.ic_lock_outline_toot); break; case "direct": visibility = "direct"; toot_visibility.setImageResource(R.drawable.ic_mail_outline_toot); break; } //The current id is set to the draft currentToId = draft.getId(); if (title != null) { if (social == UpdateAccountInfoAsyncTask.SOCIAL.GNU) title.setText(getString(R.string.queet_title)); else title.setText(getString(R.string.toot_title)); } else { if (social == UpdateAccountInfoAsyncTask.SOCIAL.GNU) setTitle(R.string.queet_title); else setTitle(R.string.toot_title); } invalidateOptionsMenu(); initialContent = toot_content.getText().toString(); toot_space_left.setText(String.valueOf(countLength(social, toot_content))); } public void redraw() { int position = imageSlider.getCurrentPagePosition(); if (position > attachments.size()) { position = attachments.size(); } sliderAdapter = new SliderAdapter(new WeakReference<>(BasePixelfedComposeActivity.this), true, attachments); imageSlider.setIndicatorAnimation(IndicatorAnimations.WORM); imageSlider.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION); imageSlider.setSliderAdapter(sliderAdapter); imageSlider.setCurrentPagePosition(position); } private void storeToot(boolean message, boolean forced) { //Nothing to store here.... String currentContent; currentContent = toot_content.getText().toString().trim(); if (!forced) { if (currentContent.length() == 0 && (attachments == null || attachments.size() < 1)) return; if (initialContent == null || initialContent.trim().equals(currentContent)) return; } Status toot = new Status(); toot.setSensitive(isSensitive); toot.setMedia_attachments(attachments); toot.setVisibility(visibility); toot.setContent(BasePixelfedComposeActivity.this, currentContent); SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String userId = accountReply != null ? accountReply.getId() : account.getId(); String instance = accountReply != null ? accountReply.getInstance() : account.getInstance(); try { if (currentToId == -1) { currentToId = new StatusStoredDAO(BasePixelfedComposeActivity.this, db).insertStatus(toot, null, userId, instance); } else { StoredStatus storedStatus = new StatusStoredDAO(BasePixelfedComposeActivity.this, db).getStatus(currentToId); if (storedStatus != null) { new StatusStoredDAO(BasePixelfedComposeActivity.this, db).updateStatus(currentToId, toot); } else { //Might have been deleted, so it needs insertion new StatusStoredDAO(BasePixelfedComposeActivity.this, db).insertStatus(toot, null, userId, instance); } } if (message) Toasty.success(BasePixelfedComposeActivity.this, getString(R.string.toast_toot_saved), Toast.LENGTH_LONG).show(); } catch (Exception e) { if (message) Toasty.error(BasePixelfedComposeActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } } @Override public void onStart() { super.onStart(); active = true; } @Override public void onStop() { super.onStop(); active = false; } protected abstract void launchPhotoEditor(Uri uri); protected abstract void displayEmojiPopup(); static class asyncPicture { String commpressedFilePath = null; WeakReference activityWeakReference; Uri uriFile; boolean error = false; UploadServiceSingleBroadcastReceiver uploadReceiver; String filename; boolean pixelfedStory; Account account; asyncPicture(Activity activity, boolean pixelfedStory, Account account, Uri uri, String filename, UploadServiceSingleBroadcastReceiver uploadReceiver) { this.activityWeakReference = new WeakReference<>(activity); this.uriFile = uri; this.uploadReceiver = uploadReceiver; this.filename = filename; this.pixelfedStory = pixelfedStory; this.account = account; if (uriFile == null) { Toasty.error(activityWeakReference.get(), activityWeakReference.get().getString(R.string.toast_error), Toast.LENGTH_SHORT).show(); error = true; } activityWeakReference.get().findViewById(R.id.compression_loader).setVisibility(View.VISIBLE); doInBackground(); } protected void doInBackground() { new Thread(() -> { if (error) { return; } commpressedFilePath = Helper.compressImagePath(activityWeakReference.get(), uriFile); Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> { activityWeakReference.get().findViewById(R.id.compression_loader).setVisibility(View.GONE); if (!error) { if (commpressedFilePath != null) { uriFile = Uri.fromFile(new File(commpressedFilePath)); } Button upload_media = this.activityWeakReference.get().findViewById(R.id.upload_media); Button toot_it = this.activityWeakReference.get().findViewById(R.id.toot_it); upload_media.setEnabled(false); toot_it.setEnabled(false); if (filename == null) { filename = Helper.getFileName(this.activityWeakReference.get(), uriFile); } filesMap.put(filename, uriFile); upload(activityWeakReference.get(), pixelfedStory, account, uriFile, filename, uploadReceiver); } }; mainHandler.post(myRunnable); }).start(); } } }