Change locked accounts to default visibility to "followers-only", and reorganizes the composer because it was getting cluttered.

main
Vavassor 7 years ago
parent e5c62e6415
commit 84b26d2b6b
  1. 345
      app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
  2. 56
      app/src/main/java/com/keylesspalace/tusky/EditTextTyped.java
  3. 1
      app/src/main/java/com/keylesspalace/tusky/MainActivity.java
  4. 129
      app/src/main/java/com/keylesspalace/tusky/SpanUtils.java
  5. 12
      app/src/main/res/layout/activity_compose.xml
  6. 2
      app/src/main/res/values/strings.xml

@ -40,11 +40,11 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar; 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.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v4.app.ActivityCompat; 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.content.res.AppCompatResources;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.Editable; import android.text.Editable;
import android.text.InputType;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
@ -72,7 +64,6 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.keylesspalace.tusky.entity.Media; import com.keylesspalace.tusky.entity.Media;
@ -92,6 +83,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Random; import java.util.Random;
import butterknife.BindView;
import butterknife.ButterKnife;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
import okhttp3.RequestBody; import okhttp3.RequestBody;
@ -110,28 +103,29 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
private static final int THUMBNAIL_SIZE = 128; // pixels private static final int THUMBNAIL_SIZE = 128; // pixels
private String inReplyToId; private String inReplyToId;
private EditText textEditor;
private LinearLayout mediaPreviewBar;
private ArrayList<QueuedMedia> mediaQueued; private ArrayList<QueuedMedia> mediaQueued;
private CountUpDownLatch waitForMediaLatch; private CountUpDownLatch waitForMediaLatch;
private boolean showMarkSensitive; private boolean showMarkSensitive;
private String statusVisibility; // The current values of the options that will be applied private String statusVisibility; // The current values of the options that will be applied
private boolean statusMarkSensitive; // to the status being composed. private boolean statusMarkSensitive; // to the status being composed.
private boolean statusHideText; // private boolean statusHideText; //
private View contentWarningBar;
private boolean statusAlreadyInFlight; // to prevent duplicate sends by mashing the send button private boolean statusAlreadyInFlight; // to prevent duplicate sends by mashing the send button
private InputContentInfoCompat currentInputContentInfo; private InputContentInfoCompat currentInputContentInfo;
private int currentFlags; 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; 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 { private static class QueuedMedia {
enum Type { 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_compose); setContentView(R.layout.activity_compose);
ButterKnife.bind(this);
// Setup the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setTitle(null); actionBar.setTitle(null);
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
@ -345,18 +220,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
actionBar.setHomeAsUpIndicator(closeIcon); actionBar.setHomeAsUpIndicator(closeIcon);
} }
SharedPreferences preferences = getPrivatePreferences(); // Setup the interface buttons.
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);
floatingBtn.setOnClickListener(new View.OnClickListener() { floatingBtn.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
sendStatus(); prepareStatus();
} }
}); });
pickBtn.setOnClickListener(new View.OnClickListener() { 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; String startingVisibility;
boolean startingHideText; boolean startingHideText;
@ -411,9 +281,9 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
startingHideText = false; startingHideText = false;
} }
postProgress = (ProgressBar) findViewById(R.id.postProgress); /* If the composer is started up as a reply to another post, override the "starting" state
postProgress.setVisibility(View.INVISIBLE); * based on what the intent from the reply request passes. */
updateNsfwButtonColor(); Intent intent = getIntent();
String[] mentionedUsernames = null; String[] mentionedUsernames = null;
inReplyToId = null; inReplyToId = null;
@ -434,30 +304,29 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames"); mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames");
if(inReplyToId != null) { if (inReplyToId != null) {
startingHideText = !intent.getStringExtra("content_warning").equals(""); startingHideText = !intent.getStringExtra("content_warning").equals("");
if(startingHideText){ if (startingHideText) {
startingContentWarning = intent.getStringExtra("content_warning"); startingContentWarning = intent.getStringExtra("content_warning");
} }
} }
} }
/* Only after the starting visibility is determined and the send button is initialised can
* the status visibility be set. */ /* 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";
}
// After the starting state is finalised, the interface can be set to reflect this state.
setStatusVisibility(startingVisibility); setStatusVisibility(startingVisibility);
postProgress.setVisibility(View.INVISIBLE);
updateNsfwButtonColor();
textEditor = createEditText(null); // new String[] { "image/gif", "image/webp" } // Setup the main text field.
setEditTextMimeTypes(null); // new String[] { "image/gif", "image/webp" }
final int mentionColour = ThemeUtils.getColor(this, R.attr.compose_mention_color); final int mentionColour = ThemeUtils.getColor(this, R.attr.compose_mention_color);
if (savedInstanceState != null) { SpanUtils.highlightSpans(textEditor.getText(), mentionColour);
restoreTextEditorState(savedInstanceState.getParcelable("textEditorState"));
highlightSpans(textEditor.getText(), mentionColour);
}
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);
textEditor.addTextChangedListener(new TextWatcher() { textEditor.addTextChangedListener(new TextWatcher() {
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
@ -469,10 +338,11 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
@Override @Override
public void afterTextChanged(Editable editable) { 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) { if (mentionedUsernames != null) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (String name : mentionedUsernames) { for (String name : mentionedUsernames) {
@ -484,11 +354,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
textEditor.setSelection(textEditor.length()); textEditor.setSelection(textEditor.length());
} }
mediaPreviewBar = (LinearLayout) findViewById(R.id.compose_media_preview_bar); // Initialise the content warning editor.
mediaQueued = new ArrayList<>();
waitForMediaLatch = new CountUpDownLatch();
contentWarningBar = findViewById(R.id.compose_content_warning_bar);
contentWarningEditor.addTextChangedListener(new TextWatcher() { contentWarningEditor.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 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) {} public void afterTextChanged(Editable s) {}
}); });
showContentWarning(startingHideText); showContentWarning(startingHideText);
if(startingContentWarning != null){ if(startingContentWarning != null){
contentWarningEditor.setText(startingContentWarning); contentWarningEditor.setText(startingContentWarning);
} }
// Initialise the empty media queue state.
mediaQueued = new ArrayList<>();
waitForMediaLatch = new CountUpDownLatch();
statusAlreadyInFlight = false; statusAlreadyInFlight = false;
// These can only be added after everything affected by the media queue is initialized. // 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() { private void toggleNsfw() {
statusMarkSensitive = !statusMarkSensitive; statusMarkSensitive = !statusMarkSensitive;
updateNsfwButtonColor(); updateNsfwButtonColor();
} }
private void updateNsfwButtonColor() { private void updateNsfwButtonColor() {
@AttrRes int attribute;
if (statusMarkSensitive) { if (statusMarkSensitive) {
nsfwBtn.setTextColor(ThemeUtils.getColor(this, R.attr.compose_nsfw_button_selected_color)); attribute = R.attr.compose_nsfw_button_selected_color;
} else { } 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() { private void disableButtons() {
@ -680,7 +584,7 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
enableButtons(); enableButtons();
} }
private void sendStatus() { private void prepareStatus() {
if (statusAlreadyInFlight) { if (statusAlreadyInFlight) {
return; 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 @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
@ -758,39 +617,21 @@ public class ComposeActivity extends BaseActivity implements ComposeOptionsFrag
.apply(); .apply();
} }
private EditText createEditText(String[] contentMimeTypes) { private void setEditTextMimeTypes(String[] contentMimeTypes) {
final String[] mimeTypes; final String[] mimeTypes;
if (contentMimeTypes == null || contentMimeTypes.length == 0) { if (contentMimeTypes == null || contentMimeTypes.length == 0) {
mimeTypes = new String[0]; mimeTypes = new String[0];
} else { } else {
mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length); mimeTypes = Arrays.copyOf(contentMimeTypes, contentMimeTypes.length);
} }
EditText editText = new android.support.v7.widget.AppCompatEditText(this) { textEditor.setMimeTypes(mimeTypes, new InputConnectionCompat.OnCommitContentListener() {
@Override @Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) { public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
final InputConnection ic = super.onCreateInputConnection(editorInfo); int flags, Bundle opts) {
EditorInfoCompat.setContentMimeTypes(editorInfo, mimeTypes); return ComposeActivity.this.onCommitContent(inputContentInfo, flags,
final InputConnectionCompat.OnCommitContentListener callback = mimeTypes);
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);
} }
}; });
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, private boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags,

@ -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() getPrivatePreferences().edit()
.putString("loggedInAccountId", loggedInAccountId) .putString("loggedInAccountId", loggedInAccountId)
.putString("loggedInAccountUsername", loggedInAccountUsername) .putString("loggedInAccountUsername", loggedInAccountUsername)
.putBoolean("loggedInAccountLocked", me.locked)
.apply(); .apply();
} }

@ -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> </LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/compose_edit_area"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
@ -55,8 +54,15 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<!--An special EditText is created at runtime here, because it has to be a modified <com.keylesspalace.tusky.EditTextTyped
* anonymous class to support image/GIF picking from the soft keyboard.--> 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 <HorizontalScrollView
android:layout_width="match_parent" android:layout_width="match_parent"

@ -137,7 +137,7 @@
<string name="visibility_public">Public: Post to public timelines</string> <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_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="visibility_direct">Direct: Post to mentioned users only</string>
<string name="pref_title_notification_settings">Notifications</string> <string name="pref_title_notification_settings">Notifications</string>

Loading…
Cancel
Save