Create polls (#1452)
* add AddPollDialog * add support for pleroma poll options * add PollPreviewView * add Poll support to drafts * add license header, cleanup * rename drawable files to correct size * fix tests * fix bug with Poll having wrong duration after delete&redraft * add input validation * grey out poll button when its disabled * code cleanup & small improvements
This commit is contained in:
parent
444df322a7
commit
51c6852492
61 changed files with 1540 additions and 76 deletions
|
@ -79,6 +79,7 @@ import com.keylesspalace.tusky.entity.Account;
|
|||
import com.keylesspalace.tusky.entity.Attachment;
|
||||
import com.keylesspalace.tusky.entity.Emoji;
|
||||
import com.keylesspalace.tusky.entity.Instance;
|
||||
import com.keylesspalace.tusky.entity.NewPoll;
|
||||
import com.keylesspalace.tusky.entity.SearchResults;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.network.MastodonApi;
|
||||
|
@ -94,9 +95,11 @@ import com.keylesspalace.tusky.util.SaveTootHelper;
|
|||
import com.keylesspalace.tusky.util.SpanUtilsKt;
|
||||
import com.keylesspalace.tusky.util.StringUtils;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.view.AddPollDialog;
|
||||
import com.keylesspalace.tusky.view.ComposeOptionsListener;
|
||||
import com.keylesspalace.tusky.view.ComposeOptionsView;
|
||||
import com.keylesspalace.tusky.view.EditTextTyped;
|
||||
import com.keylesspalace.tusky.view.PollPreviewView;
|
||||
import com.keylesspalace.tusky.view.ProgressImageView;
|
||||
import com.keylesspalace.tusky.view.TootButton;
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
|
@ -190,6 +193,7 @@ public final class ComposeActivity
|
|||
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content";
|
||||
private static final String MEDIA_ATTACHMENTS_EXTRA = "media_attachments";
|
||||
private static final String SENSITIVE_EXTRA = "sensitive";
|
||||
private static final String POLL_EXTRA = "poll";
|
||||
// Mastodon only counts URLs as this long in terms of status character limits
|
||||
static final int MAXIMUM_URL_LENGTH = 23;
|
||||
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
||||
|
@ -213,6 +217,7 @@ public final class ComposeActivity
|
|||
private ImageButton contentWarningButton;
|
||||
private ImageButton emojiButton;
|
||||
private ImageButton hideMediaToggle;
|
||||
private TextView actionAddPoll;
|
||||
private Button atButton;
|
||||
private Button hashButton;
|
||||
|
||||
|
@ -222,11 +227,14 @@ public final class ComposeActivity
|
|||
private BottomSheetBehavior emojiBehavior;
|
||||
private RecyclerView emojiView;
|
||||
|
||||
private PollPreviewView pollPreview;
|
||||
|
||||
// this only exists when a status is trying to be sent, but uploads are still occurring
|
||||
private ProgressDialog finishingUploadDialog;
|
||||
private String inReplyToId;
|
||||
private List<QueuedMedia> mediaQueued = new ArrayList<>();
|
||||
private CountUpDownLatch waitForMediaLatch;
|
||||
private NewPoll poll;
|
||||
private Status.Visibility statusVisibility; // The current values of the options that will be applied
|
||||
private boolean statusMarkSensitive; // to the status being composed.
|
||||
private boolean statusHideText;
|
||||
|
@ -239,6 +247,8 @@ public final class ComposeActivity
|
|||
private List<Emoji> emojiList;
|
||||
private CountDownLatch emojiListRetrievalLatch = new CountDownLatch(1);
|
||||
private int maximumTootCharacters = STATUS_CHARACTER_LIMIT;
|
||||
private Integer maxPollOptions = null;
|
||||
private Integer maxPollOptionLength = null;
|
||||
private @Px
|
||||
int thumbnailViewSize;
|
||||
|
||||
|
@ -369,6 +379,7 @@ public final class ComposeActivity
|
|||
|
||||
TextView actionPhotoTake = findViewById(R.id.action_photo_take);
|
||||
TextView actionPhotoPick = findViewById(R.id.action_photo_pick);
|
||||
actionAddPoll = findViewById(R.id.action_add_poll);
|
||||
|
||||
int textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary);
|
||||
|
||||
|
@ -378,8 +389,12 @@ public final class ComposeActivity
|
|||
Drawable imageIcon = new IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).color(textColor).sizeDp(18);
|
||||
actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null);
|
||||
|
||||
Drawable pollIcon = new IconicsDrawable(this, GoogleMaterial.Icon.gmd_poll).color(textColor).sizeDp(18);
|
||||
actionAddPoll.setCompoundDrawablesRelativeWithIntrinsicBounds(pollIcon, null, null, null);
|
||||
|
||||
actionPhotoTake.setOnClickListener(v -> initiateCameraApp());
|
||||
actionPhotoPick.setOnClickListener(v -> onMediaPick());
|
||||
actionAddPoll.setOnClickListener(v -> openPollDialog());
|
||||
|
||||
thumbnailViewSize = getResources().getDimensionPixelSize(R.dimen.compose_media_preview_size);
|
||||
|
||||
|
@ -507,6 +522,14 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
statusMarkSensitive = intent.getBooleanExtra(SENSITIVE_EXTRA, statusMarkSensitive);
|
||||
|
||||
if(intent.hasExtra(POLL_EXTRA) && (mediaAttachments == null || mediaAttachments.size() == 0)) {
|
||||
updatePoll(intent.getParcelableExtra(POLL_EXTRA));
|
||||
}
|
||||
|
||||
if(mediaAttachments != null && mediaAttachments.size() > 0) {
|
||||
enablePollButton(false);
|
||||
}
|
||||
}
|
||||
|
||||
// After the starting state is finalised, the interface can be set to reflect this state.
|
||||
|
@ -901,6 +924,62 @@ public final class ComposeActivity
|
|||
addMediaBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
|
||||
private void openPollDialog() {
|
||||
addMediaBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
AddPollDialog.showAddPollDialog(this, poll, maxPollOptions, maxPollOptionLength);
|
||||
}
|
||||
|
||||
public void updatePoll(NewPoll poll) {
|
||||
this.poll = poll;
|
||||
|
||||
enableButton(pickButton, false, false);
|
||||
|
||||
if(pollPreview == null) {
|
||||
|
||||
pollPreview = new PollPreviewView(this);
|
||||
|
||||
Resources resources = getResources();
|
||||
int margin = resources.getDimensionPixelSize(R.dimen.compose_media_preview_margin);
|
||||
int marginBottom = resources.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom);
|
||||
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.setMargins(margin, margin, margin, marginBottom);
|
||||
pollPreview.setLayoutParams(layoutParams);
|
||||
|
||||
mediaPreviewBar.addView(pollPreview);
|
||||
|
||||
pollPreview.setOnClickListener(v -> {
|
||||
PopupMenu popup = new PopupMenu(this, pollPreview);
|
||||
final int editId = 1;
|
||||
final int removeId = 2;
|
||||
popup.getMenu().add(0, editId, 0, R.string.edit_poll);
|
||||
popup.getMenu().add(0, removeId, 0, R.string.action_remove);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case editId:
|
||||
openPollDialog();
|
||||
break;
|
||||
case removeId:
|
||||
removePoll();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
popup.show();
|
||||
});
|
||||
}
|
||||
|
||||
pollPreview.setPoll(poll);
|
||||
|
||||
}
|
||||
|
||||
private void removePoll() {
|
||||
poll = null;
|
||||
pollPreview = null;
|
||||
enableButton(pickButton, true, true);
|
||||
mediaPreviewBar.removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVisibilityChanged(@NonNull Status.Visibility visibility) {
|
||||
composeOptionsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
|
@ -1005,7 +1084,7 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
Intent sendIntent = SendTootService.sendTootIntent(this, content, spoilerText,
|
||||
visibility, !mediaUris.isEmpty() && sensitive, mediaIds, mediaUris, mediaDescriptions, inReplyToId,
|
||||
visibility, !mediaUris.isEmpty() && sensitive, mediaIds, mediaUris, mediaDescriptions, inReplyToId, poll,
|
||||
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
|
||||
getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA),
|
||||
getIntent().getStringExtra(SAVED_JSON_URLS_EXTRA),
|
||||
|
@ -1162,6 +1241,18 @@ public final class ComposeActivity
|
|||
colorActive ? android.R.attr.textColorTertiary : R.attr.compose_media_button_disabled_tint);
|
||||
}
|
||||
|
||||
private void enablePollButton(boolean enable) {
|
||||
actionAddPoll.setEnabled(enable);
|
||||
int textColor;
|
||||
if(enable) {
|
||||
textColor = ThemeUtils.getColor(this, android.R.attr.textColorTertiary);
|
||||
} else {
|
||||
textColor = ThemeUtils.getColor(this, R.attr.compose_media_button_disabled_tint);
|
||||
}
|
||||
actionAddPoll.setTextColor(textColor);
|
||||
actionAddPoll.getCompoundDrawablesRelative()[0].setColorFilter(textColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
private void addMediaToQueue(QueuedMedia.Type type, Bitmap preview, Uri uri, long mediaSize, @Nullable String description) {
|
||||
addMediaToQueue(null, type, preview, uri, mediaSize, null, description);
|
||||
}
|
||||
|
@ -1210,6 +1301,7 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
updateHideMediaToggle();
|
||||
enablePollButton(false);
|
||||
|
||||
if (item.readyStage != QueuedMedia.ReadyStage.UPLOADED) {
|
||||
waitForMediaLatch.countUp();
|
||||
|
@ -1259,7 +1351,7 @@ public final class ComposeActivity
|
|||
final int addCaptionId = 1;
|
||||
final int removeId = 2;
|
||||
popup.getMenu().add(0, addCaptionId, 0, R.string.action_set_caption);
|
||||
popup.getMenu().add(0, removeId, 0, R.string.action_remove_media);
|
||||
popup.getMenu().add(0, removeId, 0, R.string.action_remove);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case addCaptionId:
|
||||
|
@ -1378,6 +1470,7 @@ public final class ComposeActivity
|
|||
mediaQueued.remove(item);
|
||||
if (mediaQueued.size() == 0) {
|
||||
updateHideMediaToggle();
|
||||
enablePollButton(true);
|
||||
}
|
||||
updateContentDescriptionForAllImages();
|
||||
enableButton(pickButton, true, true);
|
||||
|
@ -1685,8 +1778,9 @@ public final class ComposeActivity
|
|||
boolean contentWarningChanged = contentWarningBar.getVisibility() == View.VISIBLE &&
|
||||
!TextUtils.isEmpty(contentWarning) && !startingContentWarning.startsWith(contentWarning.toString());
|
||||
boolean mediaChanged = !mediaQueued.isEmpty();
|
||||
boolean pollChanged = poll != null;
|
||||
|
||||
if (textChanged || contentWarningChanged || mediaChanged) {
|
||||
if (textChanged || contentWarningChanged || mediaChanged || pollChanged) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.compose_save_draft)
|
||||
.setPositiveButton(R.string.action_save, (d, w) -> saveDraftAndFinish())
|
||||
|
@ -1722,7 +1816,8 @@ public final class ComposeActivity
|
|||
inReplyToId,
|
||||
getIntent().getStringExtra(REPLYING_STATUS_CONTENT_EXTRA),
|
||||
getIntent().getStringExtra(REPLYING_STATUS_AUTHOR_USERNAME_EXTRA),
|
||||
statusVisibility);
|
||||
statusVisibility,
|
||||
poll);
|
||||
finishWithoutSlideOutAnimation();
|
||||
}
|
||||
|
||||
|
@ -1808,6 +1903,8 @@ public final class ComposeActivity
|
|||
if (instanceEntity != null) {
|
||||
Integer max = instanceEntity.getMaximumTootCharacters();
|
||||
maximumTootCharacters = (max == null ? STATUS_CHARACTER_LIMIT : max);
|
||||
maxPollOptions = instanceEntity.getMaxPollOptions();
|
||||
maxPollOptionLength = instanceEntity.getMaxPollOptionLength();
|
||||
setEmojiList(instanceEntity.getEmojiList());
|
||||
updateVisibleCharactersLeft();
|
||||
}
|
||||
|
@ -1825,7 +1922,9 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
private void cacheInstanceMetadata(@NotNull AccountEntity activeAccount) {
|
||||
InstanceEntity instanceEntity = new InstanceEntity(activeAccount.getDomain(), emojiList, maximumTootCharacters);
|
||||
InstanceEntity instanceEntity = new InstanceEntity(
|
||||
activeAccount.getDomain(), emojiList, maximumTootCharacters, maxPollOptions, maxPollOptionLength
|
||||
);
|
||||
database.instanceDao().insertOrReplace(instanceEntity);
|
||||
}
|
||||
|
||||
|
@ -1840,9 +1939,18 @@ public final class ComposeActivity
|
|||
}
|
||||
|
||||
private void onFetchInstanceSuccess(Instance instance) {
|
||||
if (instance != null && instance.getMaxTootChars() != null) {
|
||||
maximumTootCharacters = instance.getMaxTootChars();
|
||||
updateVisibleCharactersLeft();
|
||||
if (instance != null) {
|
||||
|
||||
if (instance.getMaxTootChars() != null) {
|
||||
maximumTootCharacters = instance.getMaxTootChars();
|
||||
updateVisibleCharactersLeft();
|
||||
}
|
||||
|
||||
if (instance.getPollLimits() != null) {
|
||||
maxPollOptions = instance.getPollLimits().getMaxOptions();
|
||||
maxPollOptionLength = instance.getPollLimits().getMaxOptionChars();
|
||||
}
|
||||
|
||||
cacheInstanceMetadata(accountManager.getActiveAccount());
|
||||
}
|
||||
}
|
||||
|
@ -1966,7 +2074,8 @@ public final class ComposeActivity
|
|||
private ArrayList<Attachment> mediaAttachments;
|
||||
@Nullable
|
||||
private Boolean sensitive;
|
||||
|
||||
@Nullable
|
||||
private NewPoll poll;
|
||||
|
||||
public IntentBuilder savedTootUid(int uid) {
|
||||
this.savedTootUid = uid;
|
||||
|
@ -2033,6 +2142,11 @@ public final class ComposeActivity
|
|||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder poll(NewPoll poll) {
|
||||
this.poll = poll;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Intent build(Context context) {
|
||||
Intent intent = new Intent(context, ComposeActivity.class);
|
||||
|
||||
|
@ -2073,9 +2187,12 @@ public final class ComposeActivity
|
|||
if (mediaAttachments != null) {
|
||||
intent.putParcelableArrayListExtra(MEDIA_ATTACHMENTS_EXTRA, mediaAttachments);
|
||||
}
|
||||
if(sensitive != null) {
|
||||
if (sensitive != null) {
|
||||
intent.putExtra(SENSITIVE_EXTRA, sensitive);
|
||||
}
|
||||
if (poll != null) {
|
||||
intent.putExtra(POLL_EXTRA, poll);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ public final class SavedTootActivity extends BaseActivity implements SavedTootAd
|
|||
.replyingStatusAuthor(item.getInReplyToUsername())
|
||||
.replyingStatusContent(item.getInReplyToText())
|
||||
.visibility(item.getVisibility())
|
||||
.poll(item.getPoll())
|
||||
.build(this);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ public class TuskyApplication extends Application implements HasAndroidInjector
|
|||
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
|
||||
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13,
|
||||
AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16,
|
||||
AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18)
|
||||
AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19)
|
||||
.build();
|
||||
accountManager = new AccountManager(appDatabase);
|
||||
serviceLocator = new ServiceLocator() {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
*
|
||||
* 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.adapter
|
||||
|
||||
import android.text.InputFilter
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.util.onTextChanged
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
|
||||
class AddPollOptionsAdapter(
|
||||
private var options: MutableList<String>,
|
||||
private val maxOptionLength: Int,
|
||||
private val onOptionRemoved: () -> Unit,
|
||||
private val onOptionChanged: (Boolean) -> Unit
|
||||
): RecyclerView.Adapter<ViewHolder>() {
|
||||
|
||||
val pollOptions: List<String>
|
||||
get() = options.toList()
|
||||
|
||||
fun addChoice() {
|
||||
options.add("")
|
||||
notifyItemInserted(options.size - 1)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val holder = ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_add_poll_option, parent, false))
|
||||
holder.editText.filters = arrayOf(InputFilter.LengthFilter(maxOptionLength))
|
||||
|
||||
holder.editText.onTextChanged { s, _, _, _ ->
|
||||
val pos = holder.adapterPosition
|
||||
if(pos != RecyclerView.NO_POSITION) {
|
||||
options[pos] = s.toString()
|
||||
onOptionChanged(validateInput())
|
||||
}
|
||||
}
|
||||
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun getItemCount() = options.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.editText.setText(options[position])
|
||||
|
||||
holder.textInputLayout.hint = holder.textInputLayout.context.getString(R.string.poll_new_choice_hint, position + 1)
|
||||
|
||||
holder.deleteButton.visible(position > 1, View.INVISIBLE)
|
||||
|
||||
holder.deleteButton.setOnClickListener {
|
||||
holder.editText.clearFocus()
|
||||
options.removeAt(holder.adapterPosition)
|
||||
notifyItemRemoved(holder.adapterPosition)
|
||||
onOptionRemoved()
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateInput(): Boolean {
|
||||
if (options.contains("") || options.distinct().size != options.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
val textInputLayout: TextInputLayout = itemView.findViewById(R.id.optionTextInputLayout)
|
||||
val editText: TextInputEditText = itemView.findViewById(R.id.optionEditText)
|
||||
val deleteButton: ImageButton = itemView.findViewById(R.id.deleteButton)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
*
|
||||
* 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.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
|
||||
class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
||||
|
||||
private var options: List<String> = emptyList()
|
||||
private var multiple: Boolean = false
|
||||
private var clickListener: View.OnClickListener? = null
|
||||
|
||||
fun update(newOptions: List<String>, multiple: Boolean) {
|
||||
this.options = newOptions
|
||||
this.multiple = multiple
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setOnClickListener(l: View.OnClickListener?) {
|
||||
clickListener = l
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreviewViewHolder {
|
||||
return PreviewViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_poll_preview_option, parent, false))
|
||||
}
|
||||
|
||||
override fun getItemCount() = options.size
|
||||
|
||||
override fun onBindViewHolder(holder: PreviewViewHolder, position: Int) {
|
||||
val textView = holder.itemView as TextView
|
||||
|
||||
val iconId = if (multiple) {
|
||||
R.drawable.ic_check_box_outline_blank_18dp
|
||||
} else {
|
||||
R.drawable.ic_radio_button_unchecked_18dp
|
||||
}
|
||||
|
||||
val iconDrawable = ThemeUtils.getTintedDrawable(textView.context, iconId, android.R.attr.textColorTertiary)
|
||||
|
||||
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, iconDrawable, null, null, null)
|
||||
|
||||
textView.text = options[position]
|
||||
|
||||
textView.setOnClickListener(clickListener)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
|
|
@ -398,6 +398,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
.contentWarning(status.spoilerText)
|
||||
.mediaAttachments(status.attachments)
|
||||
.sensitive(status.sensitive)
|
||||
.poll(status.poll?.toNewPoll(status.createdAt))
|
||||
.build(context)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
@Database(entities = {TootEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class, ConversationEntity.class
|
||||
}, version = 18)
|
||||
}, version = 19)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
public abstract TootDao tootDao();
|
||||
|
@ -300,4 +300,14 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
}
|
||||
};
|
||||
|
||||
public static final Migration MIGRATION_18_19 = new Migration(18, 19) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptions` INTEGER");
|
||||
database.execSQL("ALTER TABLE `InstanceEntity` ADD COLUMN `maxPollOptionLength` INTEGER");
|
||||
|
||||
database.execSQL("ALTER TABLE `TootEntity` ADD COLUMN `poll` TEXT");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -25,4 +25,7 @@ import com.keylesspalace.tusky.entity.Emoji
|
|||
data class InstanceEntity(
|
||||
@field:PrimaryKey var instance: String,
|
||||
val emojiList: List<Emoji>?,
|
||||
val maximumTootCharacters: Int?)
|
||||
val maximumTootCharacters: Int?,
|
||||
val maxPollOptions: Int?,
|
||||
val maxPollOptionLength: Int?
|
||||
)
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
package com.keylesspalace.tusky.db;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.keylesspalace.tusky.entity.NewPoll;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -60,9 +62,13 @@ public class TootEntity {
|
|||
@ColumnInfo(name = "visibility")
|
||||
private final Status.Visibility visibility;
|
||||
|
||||
@Nullable
|
||||
@ColumnInfo(name = "poll")
|
||||
private final NewPoll poll;
|
||||
|
||||
public TootEntity(int uid, String text, String urls, String descriptions, String contentWarning, String inReplyToId,
|
||||
@Nullable String inReplyToText, @Nullable String inReplyToUsername,
|
||||
Status.Visibility visibility) {
|
||||
Status.Visibility visibility, @Nullable NewPoll poll) {
|
||||
this.uid = uid;
|
||||
this.text = text;
|
||||
this.urls = urls;
|
||||
|
@ -72,6 +78,7 @@ public class TootEntity {
|
|||
this.inReplyToText = inReplyToText;
|
||||
this.inReplyToUsername = inReplyToUsername;
|
||||
this.visibility = visibility;
|
||||
this.poll = poll;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
|
@ -112,8 +119,15 @@ public class TootEntity {
|
|||
return visibility;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NewPoll getPoll() {
|
||||
return poll;
|
||||
}
|
||||
|
||||
public static final class Converters {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
@TypeConverter
|
||||
public Status.Visibility visibilityFromInt(int number) {
|
||||
return Status.Visibility.byNum(number);
|
||||
|
@ -123,5 +137,15 @@ public class TootEntity {
|
|||
public int intFromVisibility(Status.Visibility visibility) {
|
||||
return visibility == null ? Status.Visibility.UNKNOWN.getNum() : visibility.getNum();
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public String pollToString(NewPoll poll) {
|
||||
return gson.toJson(poll);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public NewPoll stringToPoll(String poll) {
|
||||
return gson.fromJson(poll, NewPoll.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ data class Instance (
|
|||
val languages: List<String>,
|
||||
@SerializedName("contact_account") val contactAccount: Account,
|
||||
@SerializedName("max_toot_chars") val maxTootChars: Int?,
|
||||
@SerializedName("max_bio_chars") val maxBioChars: Int?
|
||||
@SerializedName("max_bio_chars") val maxBioChars: Int?,
|
||||
@SerializedName("poll_limits") val pollLimits: PollLimits?
|
||||
) {
|
||||
override fun hashCode(): Int {
|
||||
return uri.hashCode()
|
||||
|
@ -44,3 +45,7 @@ data class Instance (
|
|||
}
|
||||
}
|
||||
|
||||
data class PollLimits (
|
||||
@SerializedName("max_options") val maxOptions: Int?,
|
||||
@SerializedName("max_option_chars") val maxOptionChars: Int?
|
||||
)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
*
|
||||
* 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.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
data class NewStatus(
|
||||
val status: String,
|
||||
@SerializedName("spoiler_text") val warningText: String,
|
||||
@SerializedName("in_reply_to_id") val inReplyToId: String?,
|
||||
val visibility: String,
|
||||
val sensitive: Boolean,
|
||||
@SerializedName("media_ids") val mediaIds: List<String>?,
|
||||
val poll: NewPoll?
|
||||
)
|
||||
|
||||
@Parcelize
|
||||
data class NewPoll(
|
||||
val options: List<String>,
|
||||
@SerializedName("expires_in") val expiresIn: Int,
|
||||
val multiple: Boolean
|
||||
): Parcelable
|
|
@ -25,6 +25,14 @@ data class Poll(
|
|||
return copy(options = newOptions, votesCount = votesCount + choices.size, voted = true)
|
||||
}
|
||||
|
||||
fun toNewPoll(creationDate: Date) = NewPoll(
|
||||
options.map { it.title },
|
||||
expiresAt?.let {
|
||||
((it.time - creationDate.time) / 1000).toInt() + 1
|
||||
}?: 3600,
|
||||
multiple
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
data class PollOption(
|
||||
|
|
|
@ -364,14 +364,18 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
|||
timelineCases.delete(id);
|
||||
removeItem(position);
|
||||
|
||||
Intent intent = new ComposeActivity.IntentBuilder()
|
||||
ComposeActivity.IntentBuilder intentBuilder = new ComposeActivity.IntentBuilder()
|
||||
.tootText(getEditableText(status.getContent(), status.getMentions()))
|
||||
.inReplyToId(status.getInReplyToId())
|
||||
.visibility(status.getVisibility())
|
||||
.contentWarning(status.getSpoilerText())
|
||||
.mediaAttachments(status.getAttachments())
|
||||
.sensitive(status.getSensitive())
|
||||
.build(getContext());
|
||||
.sensitive(status.getSensitive());
|
||||
if(status.getPoll() != null) {
|
||||
intentBuilder.poll(status.getPoll().toNewPoll(status.getCreatedAt()));
|
||||
}
|
||||
|
||||
Intent intent = intentBuilder.build(getContext());
|
||||
startActivity(intent);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
@ -470,7 +474,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
|||
|
||||
boolean shouldFilterStatus(Status status) {
|
||||
return (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find()
|
||||
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find())));
|
||||
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find())));
|
||||
}
|
||||
|
||||
private void applyFilters(boolean refresh) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.keylesspalace.tusky.entity.Emoji;
|
|||
import com.keylesspalace.tusky.entity.Filter;
|
||||
import com.keylesspalace.tusky.entity.Instance;
|
||||
import com.keylesspalace.tusky.entity.MastoList;
|
||||
import com.keylesspalace.tusky.entity.NewStatus;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.entity.Poll;
|
||||
import com.keylesspalace.tusky.entity.Relationship;
|
||||
|
@ -43,6 +44,7 @@ import okhttp3.RequestBody;
|
|||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
|
@ -126,18 +128,12 @@ public interface MastodonApi {
|
|||
Call<Attachment> updateMedia(@Path("mediaId") String mediaId,
|
||||
@Field("description") String description);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("api/v1/statuses")
|
||||
Call<Status> createStatus(
|
||||
@Header("Authorization") String auth,
|
||||
@Header(DOMAIN_HEADER) String domain,
|
||||
@Field("status") String text,
|
||||
@Field("in_reply_to_id") String inReplyToId,
|
||||
@Field("spoiler_text") String warningText,
|
||||
@Field("visibility") String visibility,
|
||||
@Field("sensitive") Boolean sensitive,
|
||||
@Field("media_ids[]") List<String> mediaIds,
|
||||
@Header("Idempotency-Key") String idempotencyKey);
|
||||
@Header("Idempotency-Key") String idempotencyKey,
|
||||
@Body NewStatus status);
|
||||
|
||||
@GET("api/v1/statuses/{id}")
|
||||
Call<Status> status(@Path("id") String statusId);
|
||||
|
|
|
@ -95,6 +95,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
|
|||
citedStatusId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null, account, 0)
|
||||
|
||||
context.startService(sendIntent)
|
||||
|
|
|
@ -22,6 +22,8 @@ import com.keylesspalace.tusky.db.AccountEntity
|
|||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import com.keylesspalace.tusky.entity.NewStatus
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.SaveTootHelper
|
||||
|
@ -131,16 +133,21 @@ class SendTootService : Service(), Injectable {
|
|||
|
||||
tootToSend.retries++
|
||||
|
||||
val sendCall = mastodonApi.createStatus(
|
||||
"Bearer " + account.accessToken,
|
||||
account.domain,
|
||||
val newStatus = NewStatus(
|
||||
tootToSend.text,
|
||||
tootToSend.inReplyToId,
|
||||
tootToSend.warningText,
|
||||
tootToSend.inReplyToId,
|
||||
tootToSend.visibility,
|
||||
tootToSend.sensitive,
|
||||
tootToSend.mediaIds,
|
||||
tootToSend.idempotencyKey
|
||||
tootToSend.poll
|
||||
)
|
||||
|
||||
val sendCall = mastodonApi.createStatus(
|
||||
"Bearer " + account.accessToken,
|
||||
account.domain,
|
||||
tootToSend.idempotencyKey,
|
||||
newStatus
|
||||
)
|
||||
|
||||
|
||||
|
@ -243,7 +250,8 @@ class SendTootService : Service(), Injectable {
|
|||
toot.inReplyToId,
|
||||
toot.replyingStatusContent,
|
||||
toot.replyingStatusAuthorUsername,
|
||||
Status.Visibility.byString(toot.visibility))
|
||||
Status.Visibility.byString(toot.visibility),
|
||||
toot.poll)
|
||||
}
|
||||
|
||||
private fun cancelSendingIntent(tootId: Int): PendingIntent {
|
||||
|
@ -277,6 +285,7 @@ class SendTootService : Service(), Injectable {
|
|||
mediaUris: List<Uri>,
|
||||
mediaDescriptions: List<String>,
|
||||
inReplyToId: String?,
|
||||
poll: NewPoll?,
|
||||
replyingStatusContent: String?,
|
||||
replyingStatusAuthorUsername: String?,
|
||||
savedJsonUrls: String?,
|
||||
|
@ -295,6 +304,7 @@ class SendTootService : Service(), Injectable {
|
|||
mediaUris.map { it.toString() },
|
||||
mediaDescriptions,
|
||||
inReplyToId,
|
||||
poll,
|
||||
replyingStatusContent,
|
||||
replyingStatusAuthorUsername,
|
||||
savedJsonUrls,
|
||||
|
@ -337,6 +347,7 @@ data class TootToSend(val text: String,
|
|||
val mediaUris: List<String>,
|
||||
val mediaDescriptions: List<String>,
|
||||
val inReplyToId: String?,
|
||||
val poll: NewPoll?,
|
||||
val replyingStatusContent: String?,
|
||||
val replyingStatusAuthorUsername: String?,
|
||||
val savedJsonUrls: String?,
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.google.gson.reflect.TypeToken;
|
|||
import com.keylesspalace.tusky.BuildConfig;
|
||||
import com.keylesspalace.tusky.db.TootDao;
|
||||
import com.keylesspalace.tusky.db.TootEntity;
|
||||
import com.keylesspalace.tusky.entity.NewPoll;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -41,17 +42,18 @@ public final class SaveTootHelper {
|
|||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public boolean saveToot(@NonNull String content,
|
||||
@NonNull String contentWarning,
|
||||
@Nullable String savedJsonUrls,
|
||||
@NonNull List<String> mediaUris,
|
||||
@NonNull List<String> mediaDescriptions,
|
||||
int savedTootUid,
|
||||
@Nullable String inReplyToId,
|
||||
@Nullable String replyingStatusContent,
|
||||
@Nullable String replyingStatusAuthorUsername,
|
||||
@NonNull Status.Visibility statusVisibility) {
|
||||
@NonNull String contentWarning,
|
||||
@Nullable String savedJsonUrls,
|
||||
@NonNull List<String> mediaUris,
|
||||
@NonNull List<String> mediaDescriptions,
|
||||
int savedTootUid,
|
||||
@Nullable String inReplyToId,
|
||||
@Nullable String replyingStatusContent,
|
||||
@Nullable String replyingStatusAuthorUsername,
|
||||
@NonNull Status.Visibility statusVisibility,
|
||||
@Nullable NewPoll poll) {
|
||||
|
||||
if (TextUtils.isEmpty(content) && mediaUris.isEmpty()) {
|
||||
if (TextUtils.isEmpty(content) && mediaUris.isEmpty() && poll == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -86,7 +88,8 @@ public final class SaveTootHelper {
|
|||
inReplyToId,
|
||||
replyingStatusContent,
|
||||
replyingStatusAuthorUsername,
|
||||
statusVisibility);
|
||||
statusVisibility,
|
||||
poll);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
|
|
102
app/src/main/java/com/keylesspalace/tusky/view/AddPollDialog.kt
Normal file
102
app/src/main/java/com/keylesspalace/tusky/view/AddPollDialog.kt
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
*
|
||||
* 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>. */
|
||||
|
||||
@file:JvmName("AddPollDialog")
|
||||
|
||||
package com.keylesspalace.tusky.view
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.keylesspalace.tusky.ComposeActivity
|
||||
import com.keylesspalace.tusky.adapter.AddPollOptionsAdapter
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import kotlinx.android.synthetic.main.dialog_add_poll.view.*
|
||||
import android.view.WindowManager
|
||||
import com.keylesspalace.tusky.R
|
||||
|
||||
private const val DEFAULT_MAX_OPTION_COUNT = 4
|
||||
private const val DEFAULT_MAX_OPTION_LENGTH = 25
|
||||
|
||||
fun showAddPollDialog(
|
||||
activity: ComposeActivity,
|
||||
poll: NewPoll?,
|
||||
maxOptionCount: Int?,
|
||||
maxOptionLength: Int?
|
||||
) {
|
||||
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_add_poll, null)
|
||||
|
||||
val dialog = AlertDialog.Builder(activity)
|
||||
.setIcon(R.drawable.ic_poll_24dp)
|
||||
.setTitle(R.string.create_poll_title)
|
||||
.setView(view)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
|
||||
val adapter = AddPollOptionsAdapter(
|
||||
options = poll?.options?.toMutableList() ?: mutableListOf("", ""),
|
||||
maxOptionLength = maxOptionLength ?: DEFAULT_MAX_OPTION_LENGTH,
|
||||
onOptionRemoved = {
|
||||
view.addChoiceButton.isEnabled = true
|
||||
},
|
||||
onOptionChanged = { valid ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid
|
||||
}
|
||||
)
|
||||
|
||||
view.pollChoices.adapter = adapter
|
||||
|
||||
view.addChoiceButton.setOnClickListener {
|
||||
if (adapter.itemCount < maxOptionCount ?: DEFAULT_MAX_OPTION_COUNT) {
|
||||
adapter.addChoice()
|
||||
}
|
||||
if (adapter.itemCount >= maxOptionCount ?: DEFAULT_MAX_OPTION_COUNT) {
|
||||
it.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
val pollDurationId = activity.resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||
it <= poll?.expiresIn ?: 0
|
||||
}
|
||||
|
||||
view.pollDurationSpinner.setSelection(pollDurationId)
|
||||
|
||||
view.multipleChoicesCheckBox.isChecked = poll?.multiple ?: false
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val button = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
button.setOnClickListener {
|
||||
val selectedPollDurationId = view.pollDurationSpinner.selectedItemPosition
|
||||
|
||||
val pollDuration = activity.resources.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
|
||||
activity.updatePoll(
|
||||
NewPoll(
|
||||
options = adapter.pollOptions,
|
||||
expiresIn = pollDuration,
|
||||
multiple = view.multipleChoicesCheckBox.isChecked
|
||||
)
|
||||
)
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
|
||||
// make the dialog focusable so the keyboard does not stay behind it
|
||||
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
*
|
||||
* 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.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.PreviewPollOptionsAdapter
|
||||
import com.keylesspalace.tusky.entity.NewPoll
|
||||
import kotlinx.android.synthetic.main.view_poll_preview.view.*
|
||||
|
||||
class PollPreviewView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val adapter = PreviewPollOptionsAdapter()
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_poll_preview, this)
|
||||
|
||||
orientation = VERTICAL
|
||||
|
||||
setBackgroundResource(R.drawable.card_frame)
|
||||
|
||||
val padding = resources.getDimensionPixelSize(R.dimen.poll_preview_padding)
|
||||
|
||||
setPadding(padding, padding, padding, padding)
|
||||
|
||||
pollPreviewOptions.adapter = adapter
|
||||
|
||||
}
|
||||
|
||||
fun setPoll(poll: NewPoll){
|
||||
adapter.update(poll.options, poll.multiple)
|
||||
|
||||
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||
it <= poll.expiresIn
|
||||
}
|
||||
pollDurationPreview.text = resources.getStringArray(R.array.poll_duration_names)[pollDurationId]
|
||||
|
||||
}
|
||||
|
||||
override fun setOnClickListener(l: OnClickListener?) {
|
||||
super.setOnClickListener(l)
|
||||
adapter.setOnClickListener(l)
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue