Change locked accounts to default visibility to "followers-only", and reorganizes the composer because it was getting cluttered.
This commit is contained in:
parent
b6cf99602b
commit
ab76121692
6 changed files with 290 additions and 257 deletions
|
@ -40,11 +40,11 @@ import android.os.Parcel;
|
|||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
|
@ -54,17 +54,9 @@ import android.support.v7.app.ActionBar;
|
|||
import android.support.v7.content.res.AppCompatResources;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
@ -72,7 +64,6 @@ import android.widget.ImageButton;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Media;
|
||||
|
@ -92,6 +83,8 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
|
@ -110,28 +103,29 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
private static final int THUMBNAIL_SIZE = 128; // pixels
|
||||
|
||||
private String inReplyToId;
|
||||
private EditText textEditor;
|
||||
private LinearLayout mediaPreviewBar;
|
||||
private ArrayList<QueuedMedia> mediaQueued;
|
||||
private CountUpDownLatch waitForMediaLatch;
|
||||
private boolean showMarkSensitive;
|
||||
private String statusVisibility; // The current values of the options that will be applied
|
||||
private boolean statusMarkSensitive; // to the status being composed.
|
||||
private boolean statusHideText; //
|
||||
private View contentWarningBar;
|
||||
private boolean statusAlreadyInFlight; // to prevent duplicate sends by mashing the send button
|
||||
private InputContentInfoCompat currentInputContentInfo;
|
||||
private int currentFlags;
|
||||
private ProgressDialog finishingUploadDialog;
|
||||
private EditText contentWarningEditor;
|
||||
private TextView charactersLeft;
|
||||
private Button floatingBtn;
|
||||
private ImageButton pickBtn;
|
||||
private ImageButton takeBtn;
|
||||
private Button nsfwBtn;
|
||||
private ProgressBar postProgress;
|
||||
private ImageButton visibilityBtn;
|
||||
private Uri photoUploadUri;
|
||||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||
private ProgressDialog finishingUploadDialog;
|
||||
@BindView(R.id.compose_edit_field) EditTextTyped textEditor;
|
||||
@BindView(R.id.compose_media_preview_bar) LinearLayout mediaPreviewBar;
|
||||
@BindView(R.id.compose_content_warning_bar) View contentWarningBar;
|
||||
@BindView(R.id.field_content_warning) EditText contentWarningEditor;
|
||||
@BindView(R.id.characters_left) TextView charactersLeft;
|
||||
@BindView(R.id.floating_btn) Button floatingBtn;
|
||||
@BindView(R.id.compose_photo_pick) ImageButton pickBtn;
|
||||
@BindView(R.id.compose_photo_take) ImageButton takeBtn;
|
||||
@BindView(R.id.action_toggle_nsfw) Button nsfwBtn;
|
||||
@BindView(R.id.postProgress) ProgressBar postProgress;
|
||||
@BindView(R.id.action_toggle_visibility) ImageButton visibilityBtn;
|
||||
|
||||
private static class QueuedMedia {
|
||||
enum Type {
|
||||
|
@ -207,135 +201,16 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
};
|
||||
}
|
||||
|
||||
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
|
||||
View.OnClickListener listener) {
|
||||
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(descriptionId),
|
||||
Snackbar.LENGTH_SHORT);
|
||||
bar.setAction(actionId, listener);
|
||||
bar.show();
|
||||
}
|
||||
|
||||
private void displayTransientError(@StringRes int stringId) {
|
||||
Snackbar.make(findViewById(R.id.activity_compose), stringId, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private static class FindCharsResult {
|
||||
int charIndex;
|
||||
int stringIndex;
|
||||
|
||||
FindCharsResult() {
|
||||
charIndex = -1;
|
||||
stringIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static FindCharsResult findChars(String string, int fromIndex, char[] chars) {
|
||||
FindCharsResult result = new FindCharsResult();
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex; i < length; i++) {
|
||||
char c = string.charAt(i);
|
||||
for (int j = 0; j < chars.length; j++) {
|
||||
if (chars[j] == c) {
|
||||
result.charIndex = j;
|
||||
result.stringIndex = i;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static FindCharsResult findStart(String string, int fromIndex, char[] chars) {
|
||||
final int length = string.length();
|
||||
while (fromIndex < length) {
|
||||
FindCharsResult found = findChars(string, fromIndex, chars);
|
||||
int i = found.stringIndex;
|
||||
if (i < 0) {
|
||||
break;
|
||||
} else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) {
|
||||
return found;
|
||||
} else {
|
||||
fromIndex = i + 1;
|
||||
}
|
||||
}
|
||||
return new FindCharsResult();
|
||||
}
|
||||
|
||||
private static int findEndOfHashtag(String string, int fromIndex) {
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex + 1; i < length;) {
|
||||
int codepoint = string.codePointAt(i);
|
||||
if (Character.isWhitespace(codepoint)) {
|
||||
return i;
|
||||
} else if (codepoint == '#') {
|
||||
return -1;
|
||||
}
|
||||
i += Character.charCount(codepoint);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int findEndOfMention(String string, int fromIndex) {
|
||||
int atCount = 0;
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex + 1; i < length;) {
|
||||
int codepoint = string.codePointAt(i);
|
||||
if (Character.isWhitespace(codepoint)) {
|
||||
return i;
|
||||
} else if (codepoint == '@') {
|
||||
atCount += 1;
|
||||
if (atCount >= 2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
i += Character.charCount(codepoint);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static void highlightSpans(Spannable text, int colour) {
|
||||
// Strip all existing colour spans.
|
||||
int n = text.length();
|
||||
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
|
||||
for (int i = oldSpans.length - 1; i >= 0; i--) {
|
||||
text.removeSpan(oldSpans[i]);
|
||||
}
|
||||
// Colour the mentions and hashtags.
|
||||
String string = text.toString();
|
||||
int start;
|
||||
int end = 0;
|
||||
while (end < n) {
|
||||
char[] chars = { '#', '@' };
|
||||
FindCharsResult found = findStart(string, end, chars);
|
||||
start = found.stringIndex;
|
||||
if (start < 0) {
|
||||
break;
|
||||
}
|
||||
if (found.charIndex == 0) {
|
||||
end = findEndOfHashtag(string, start);
|
||||
} else if (found.charIndex == 1) {
|
||||
end = findEndOfMention(string, start);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (end < 0) {
|
||||
break;
|
||||
}
|
||||
text.setSpan(new ForegroundColorSpan(colour), start, end,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_compose);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
// Setup the toolbar.
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(null);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
@ -345,18 +220,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
actionBar.setHomeAsUpIndicator(closeIcon);
|
||||
}
|
||||
|
||||
SharedPreferences preferences = getPrivatePreferences();
|
||||
|
||||
floatingBtn = (Button) findViewById(R.id.floating_btn);
|
||||
pickBtn = (ImageButton) findViewById(R.id.compose_photo_pick);
|
||||
takeBtn = (ImageButton) findViewById(R.id.compose_photo_take);
|
||||
nsfwBtn = (Button) findViewById(R.id.action_toggle_nsfw);
|
||||
visibilityBtn = (ImageButton) findViewById(R.id.action_toggle_visibility);
|
||||
|
||||
// Setup the interface buttons.
|
||||
floatingBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
sendStatus();
|
||||
prepareStatus();
|
||||
}
|
||||
});
|
||||
pickBtn.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -384,7 +252,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
}
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
/* Initialise all the state, or restore it from a previous run, to determine a "starting"
|
||||
* state. */
|
||||
SharedPreferences preferences = getPrivatePreferences();
|
||||
|
||||
String startingVisibility;
|
||||
boolean startingHideText;
|
||||
|
@ -411,9 +281,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
startingHideText = false;
|
||||
}
|
||||
|
||||
postProgress = (ProgressBar) findViewById(R.id.postProgress);
|
||||
postProgress.setVisibility(View.INVISIBLE);
|
||||
updateNsfwButtonColor();
|
||||
/* If the composer is started up as a reply to another post, override the "starting" state
|
||||
* based on what the intent from the reply request passes. */
|
||||
Intent intent = getIntent();
|
||||
|
||||
String[] mentionedUsernames = null;
|
||||
inReplyToId = null;
|
||||
|
@ -434,30 +304,29 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
|
||||
mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames");
|
||||
|
||||
if(inReplyToId != null) {
|
||||
if (inReplyToId != null) {
|
||||
startingHideText = !intent.getStringExtra("content_warning").equals("");
|
||||
if(startingHideText){
|
||||
if (startingHideText) {
|
||||
startingContentWarning = intent.getStringExtra("content_warning");
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Only after the starting visibility is determined and the send button is initialised can
|
||||
* the status visibility be set. */
|
||||
setStatusVisibility(startingVisibility);
|
||||
|
||||
textEditor = createEditText(null); // new String[] { "image/gif", "image/webp" }
|
||||
final int mentionColour = ThemeUtils.getColor(this, R.attr.compose_mention_color);
|
||||
if (savedInstanceState != null) {
|
||||
restoreTextEditorState(savedInstanceState.getParcelable("textEditorState"));
|
||||
highlightSpans(textEditor.getText(), mentionColour);
|
||||
/* If the currently logged in account is locked, its posts should default to private. This
|
||||
* should override even the reply settings, so this must be done after those are set up. */
|
||||
if (preferences.getBoolean("loggedInAccountLocked", false)) {
|
||||
startingVisibility = "private";
|
||||
}
|
||||
RelativeLayout editArea = (RelativeLayout) findViewById(R.id.compose_edit_area);
|
||||
/* Adding this at index zero because it implicitly gives it the lowest input priority. So,
|
||||
* when media previews are added in front of the editor, they can receive click events
|
||||
* without the text editor stealing the events from behind them. */
|
||||
editArea.addView(textEditor, 0);
|
||||
contentWarningEditor = (EditText) findViewById(R.id.field_content_warning);
|
||||
charactersLeft = (TextView) findViewById(R.id.characters_left);
|
||||
|
||||
// After the starting state is finalised, the interface can be set to reflect this state.
|
||||
setStatusVisibility(startingVisibility);
|
||||
postProgress.setVisibility(View.INVISIBLE);
|
||||
updateNsfwButtonColor();
|
||||
|
||||
// Setup the main text field.
|
||||
setEditTextMimeTypes(null); // new String[] { "image/gif", "image/webp" }
|
||||
final int mentionColour = ThemeUtils.getColor(this, R.attr.compose_mention_color);
|
||||
SpanUtils.highlightSpans(textEditor.getText(), mentionColour);
|
||||
textEditor.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
|
@ -469,10 +338,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
highlightSpans(editable, mentionColour);
|
||||
SpanUtils.highlightSpans(editable, mentionColour);
|
||||
}
|
||||
});
|
||||
|
||||
// Add any mentions to the text field when a reply is first composed.
|
||||
if (mentionedUsernames != null) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String name : mentionedUsernames) {
|
||||
|
@ -484,11 +354,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
textEditor.setSelection(textEditor.length());
|
||||
}
|
||||
|
||||
mediaPreviewBar = (LinearLayout) findViewById(R.id.compose_media_preview_bar);
|
||||
mediaQueued = new ArrayList<>();
|
||||
waitForMediaLatch = new CountUpDownLatch();
|
||||
|
||||
contentWarningBar = findViewById(R.id.compose_content_warning_bar);
|
||||
// Initialise the content warning editor.
|
||||
contentWarningEditor.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
@ -502,11 +368,13 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
showContentWarning(startingHideText);
|
||||
|
||||
if(startingContentWarning != null){
|
||||
contentWarningEditor.setText(startingContentWarning);
|
||||
}
|
||||
|
||||
// Initialise the empty media queue state.
|
||||
mediaQueued = new ArrayList<>();
|
||||
waitForMediaLatch = new CountUpDownLatch();
|
||||
statusAlreadyInFlight = false;
|
||||
|
||||
// These can only be added after everything affected by the media queue is initialized.
|
||||
|
@ -564,17 +432,53 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
ArrayList<SavedQueuedMedia> savedMediaQueued = new ArrayList<>();
|
||||
for (QueuedMedia item : mediaQueued) {
|
||||
savedMediaQueued.add(new SavedQueuedMedia(item.type, item.uri, item.preview,
|
||||
item.mediaSize));
|
||||
}
|
||||
outState.putParcelableArrayList("savedMediaQueued", savedMediaQueued);
|
||||
outState.putBoolean("showMarkSensitive", showMarkSensitive);
|
||||
outState.putString("statusVisibility", statusVisibility);
|
||||
outState.putBoolean("statusMarkSensitive", statusMarkSensitive);
|
||||
outState.putBoolean("statusHideText", statusHideText);
|
||||
if (currentInputContentInfo != null) {
|
||||
outState.putParcelable("commitContentInputContentInfo",
|
||||
(Parcelable) currentInputContentInfo.unwrap());
|
||||
outState.putInt("commitContentFlags", currentFlags);
|
||||
}
|
||||
currentInputContentInfo = null;
|
||||
currentFlags = 0;
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
|
||||
View.OnClickListener listener) {
|
||||
Snackbar bar = Snackbar.make(findViewById(R.id.activity_compose), getString(descriptionId),
|
||||
Snackbar.LENGTH_SHORT);
|
||||
bar.setAction(actionId, listener);
|
||||
bar.show();
|
||||
}
|
||||
|
||||
private void displayTransientError(@StringRes int stringId) {
|
||||
Snackbar.make(findViewById(R.id.activity_compose), stringId, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void toggleNsfw() {
|
||||
statusMarkSensitive = !statusMarkSensitive;
|
||||
updateNsfwButtonColor();
|
||||
}
|
||||
|
||||
private void updateNsfwButtonColor() {
|
||||
@AttrRes int attribute;
|
||||
if (statusMarkSensitive) {
|
||||
nsfwBtn.setTextColor(ThemeUtils.getColor(this, R.attr.compose_nsfw_button_selected_color));
|
||||
attribute = R.attr.compose_nsfw_button_selected_color;
|
||||
} else {
|
||||
nsfwBtn.setTextColor(ThemeUtils.getColor(this, R.attr.compose_nsfw_button_color));
|
||||
attribute = R.attr.compose_nsfw_button_color;
|
||||
}
|
||||
nsfwBtn.setTextColor(ThemeUtils.getColor(this, attribute));
|
||||
}
|
||||
|
||||
private void disableButtons() {
|
||||
|
@ -680,7 +584,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
enableButtons();
|
||||
}
|
||||
|
||||
private void sendStatus() {
|
||||
private void prepareStatus() {
|
||||
if (statusAlreadyInFlight) {
|
||||
return;
|
||||
}
|
||||
|
@ -700,51 +604,6 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
ArrayList<SavedQueuedMedia> savedMediaQueued = new ArrayList<>();
|
||||
for (QueuedMedia item : mediaQueued) {
|
||||
savedMediaQueued.add(new SavedQueuedMedia(item.type, item.uri, item.preview,
|
||||
item.mediaSize));
|
||||
}
|
||||
outState.putParcelableArrayList("savedMediaQueued", savedMediaQueued);
|
||||
outState.putBoolean("showMarkSensitive", showMarkSensitive);
|
||||
outState.putString("statusVisibility", statusVisibility);
|
||||
outState.putBoolean("statusMarkSensitive", statusMarkSensitive);
|
||||
outState.putBoolean("statusHideText", statusHideText);
|
||||
outState.putParcelable("textEditorState", saveTextEditorState());
|
||||
if (currentInputContentInfo != null) {
|
||||
outState.putParcelable("commitContentInputContentInfo",
|
||||
(Parcelable) currentInputContentInfo.unwrap());
|
||||
outState.putInt("commitContentFlags", currentFlags);
|
||||
}
|
||||
currentInputContentInfo = null;
|
||||
currentFlags = 0;
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
private Parcelable saveTextEditorState() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("text", textEditor.getText().toString());
|
||||
bundle.putInt("selectionStart", textEditor.getSelectionStart());
|
||||
bundle.putInt("selectionEnd", textEditor.getSelectionEnd());
|
||||
return bundle;
|
||||
}
|
||||
|
||||
private void restoreTextEditorState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
textEditor.setText(bundle.getString("text"));
|
||||
int start = bundle.getInt("selectionStart");
|
||||
int end = bundle.getInt("selectionEnd");
|
||||
if (start != -1) {
|
||||
if (end != -1) {
|
||||
textEditor.setSelection(start, end);
|
||||
} else {
|
||||
textEditor.setSelection(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
@ -758,39 +617,21 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
|
|||
.apply();
|
||||
}
|
||||
|
||||
private EditText createEditText(String[] contentMimeTypes) {
|
||||
private void setEditTextMimeTypes(String[] contentMimeTypes) {
|
||||
final String[] mimeTypes;
|
||||
if (contentMimeTypes == null || contentMimeTypes.length == 0) {
|
||||
mimeTypes = new String[0];
|
||||
} else {
|
||||
mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length);
|
||||
}
|
||||
EditText editText = new android.support.v7.widget.AppCompatEditText(this) {
|
||||
textEditor.setMimeTypes(mimeTypes, new InputConnectionCompat.OnCommitContentListener() {
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||
final InputConnection ic = super.onCreateInputConnection(editorInfo);
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
|
||||
final InputConnectionCompat.OnCommitContentListener callback =
|
||||
new InputConnectionCompat.OnCommitContentListener() {
|
||||
@Override
|
||||
public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
|
||||
int flags, Bundle opts) {
|
||||
return ComposeActivity.this.onCommitContent(inputContentInfo, flags,
|
||||
mimeTypes);
|
||||
}
|
||||
};
|
||||
return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
|
||||
public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
|
||||
int flags, Bundle opts) {
|
||||
return ComposeActivity.this.onCommitContent(inputContentInfo, flags,
|
||||
mimeTypes);
|
||||
}
|
||||
};
|
||||
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
editText.setLayoutParams(layoutParams);
|
||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
|
||||
editText.setEms(10);
|
||||
editText.setBackgroundColor(0);
|
||||
editText.setGravity(Gravity.START | Gravity.TOP);
|
||||
editText.setHint(R.string.hint_compose);
|
||||
return editText;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,
|
||||
|
|
56
app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java
Normal file
56
app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
public class EditTextTyped extends AppCompatEditText {
|
||||
InputConnectionCompat.OnCommitContentListener onCommitContentListener;
|
||||
String[] mimeTypes;
|
||||
|
||||
public EditTextTyped(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public EditTextTyped(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
|
||||
public void setMimeTypes(String[] types,
|
||||
InputConnectionCompat.OnCommitContentListener listener) {
|
||||
mimeTypes = types;
|
||||
onCommitContentListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
|
||||
InputConnection connection = super.onCreateInputConnection(editorInfo);
|
||||
if (onCommitContentListener != null) {
|
||||
Assert.expect(mimeTypes != null);
|
||||
EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes);
|
||||
return InputConnectionCompat.createWrapper(connection, editorInfo,
|
||||
onCommitContentListener);
|
||||
} else {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -502,6 +502,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
|
|||
getPrivatePreferences().edit()
|
||||
.putString("loggedInAccountId", loggedInAccountId)
|
||||
.putString("loggedInAccountUsername", loggedInAccountUsername)
|
||||
.putBoolean("loggedInAccountLocked", me.locked)
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
|
129
app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
Normal file
129
app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
class SpanUtils {
|
||||
private static class FindCharsResult {
|
||||
int charIndex;
|
||||
int stringIndex;
|
||||
|
||||
FindCharsResult() {
|
||||
charIndex = -1;
|
||||
stringIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static FindCharsResult findChars(String string, int fromIndex, char[] chars) {
|
||||
FindCharsResult result = new FindCharsResult();
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex; i < length; i++) {
|
||||
char c = string.charAt(i);
|
||||
for (int j = 0; j < chars.length; j++) {
|
||||
if (chars[j] == c) {
|
||||
result.charIndex = j;
|
||||
result.stringIndex = i;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static FindCharsResult findStart(String string, int fromIndex, char[] chars) {
|
||||
final int length = string.length();
|
||||
while (fromIndex < length) {
|
||||
FindCharsResult found = findChars(string, fromIndex, chars);
|
||||
int i = found.stringIndex;
|
||||
if (i < 0) {
|
||||
break;
|
||||
} else if (i == 0 || i >= 1 && Character.isWhitespace(string.codePointBefore(i))) {
|
||||
return found;
|
||||
} else {
|
||||
fromIndex = i + 1;
|
||||
}
|
||||
}
|
||||
return new FindCharsResult();
|
||||
}
|
||||
|
||||
private static int findEndOfHashtag(String string, int fromIndex) {
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex + 1; i < length;) {
|
||||
int codepoint = string.codePointAt(i);
|
||||
if (Character.isWhitespace(codepoint)) {
|
||||
return i;
|
||||
} else if (codepoint == '#') {
|
||||
return -1;
|
||||
}
|
||||
i += Character.charCount(codepoint);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int findEndOfMention(String string, int fromIndex) {
|
||||
int atCount = 0;
|
||||
final int length = string.length();
|
||||
for (int i = fromIndex + 1; i < length;) {
|
||||
int codepoint = string.codePointAt(i);
|
||||
if (Character.isWhitespace(codepoint)) {
|
||||
return i;
|
||||
} else if (codepoint == '@') {
|
||||
atCount += 1;
|
||||
if (atCount >= 2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
i += Character.charCount(codepoint);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static void highlightSpans(Spannable text, int colour) {
|
||||
// Strip all existing colour spans.
|
||||
int n = text.length();
|
||||
ForegroundColorSpan[] oldSpans = text.getSpans(0, n, ForegroundColorSpan.class);
|
||||
for (int i = oldSpans.length - 1; i >= 0; i--) {
|
||||
text.removeSpan(oldSpans[i]);
|
||||
}
|
||||
// Colour the mentions and hashtags.
|
||||
String string = text.toString();
|
||||
int start;
|
||||
int end = 0;
|
||||
while (end < n) {
|
||||
char[] chars = { '#', '@' };
|
||||
FindCharsResult found = findStart(string, end, chars);
|
||||
start = found.stringIndex;
|
||||
if (start < 0) {
|
||||
break;
|
||||
}
|
||||
if (found.charIndex == 0) {
|
||||
end = findEndOfHashtag(string, start);
|
||||
} else if (found.charIndex == 1) {
|
||||
end = findEndOfMention(string, start);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (end < 0) {
|
||||
break;
|
||||
}
|
||||
text.setSpan(new ForegroundColorSpan(colour), start, end,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,7 +47,6 @@
|
|||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/compose_edit_area"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
|
@ -55,8 +54,15 @@
|
|||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<!--An special EditText is created at runtime here, because it has to be a modified
|
||||
* anonymous class to support image/GIF picking from the soft keyboard.-->
|
||||
<com.keylesspalace.tusky.EditTextTyped
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/compose_edit_field"
|
||||
android:background="@android:color/transparent"
|
||||
android:ems="10"
|
||||
android:gravity="start|top"
|
||||
android:hint="@string/hint_compose"
|
||||
android:inputType="text|textMultiLine|textCapSentences" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
|
||||
<string name="visibility_public">Public: Post to public timelines</string>
|
||||
<string name="visibility_unlisted">Unlisted: Do not show in public timelines</string>
|
||||
<string name="visibility_private">Private: Post to followers only</string>
|
||||
<string name="visibility_private">Followers-Only: Post to followers only</string>
|
||||
<string name="visibility_direct">Direct: Post to mentioned users only</string>
|
||||
|
||||
<string name="pref_title_notification_settings">Notifications</string>
|
||||
|
|
Loading…
Reference in a new issue