Polls part 1 - displaying in timelines and voting (#1200)

* add entity classes

* change data models and add database migration

* add polls to StatusViewData

* show poll results

* add methods for vote handling

* add voting interface

* enable voting in TimelineFragment

* update polls immediately

* enable custom emojis for poll options

* enable voting from search fragment

* add voting layout to detailed statuses

* fix tests

* enable voting in ViewThreadFragment

* enable voting in ConversationsFragment

* small refactor for StatusBaseViewHolder
This commit is contained in:
Konrad Pozniak 2019-04-22 10:11:00 +02:00 committed by GitHub
commit fd7471f2ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1637 additions and 68 deletions

View file

@ -46,6 +46,7 @@ import com.keylesspalace.tusky.db.AccountEntity;
import com.keylesspalace.tusky.db.AccountManager;
import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Poll;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
@ -428,6 +429,24 @@ public class NotificationsFragment extends SFragment implements
updateAdapter();
}
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
final Notification notification = notifications.get(position).asRight();
final Status status = notification.getStatus();
timelineCases.voteInPoll(status, choices)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this)))
.subscribe(
(newPoll) -> setVoteForPoll(position, newPoll),
(t) -> Log.d(TAG,
"Failed to vote in poll: " + status.getId(), t)
);
}
private void setVoteForPoll(int position, Poll poll) {
// TODO
}
@Override
public void onMore(@NonNull View view, int position) {
Notification notification = notifications.get(position).asRight();

View file

@ -232,10 +232,6 @@ class SearchFragment : SFragment(), StatusActionListener {
searchRecyclerView.post { searchAdapter.notifyItemChanged(position, updatedStatus) }
}
companion object {
const val TAG = "SearchFragment"
}
override fun onViewAccount(id: String) {
val intent = AccountActivity.getIntent(requireContext(), id)
startActivity(intent)
@ -247,4 +243,28 @@ class SearchFragment : SFragment(), StatusActionListener {
startActivity(intent)
}
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
val status = searchAdapter.getStatusAtPosition(position)
if (status != null) {
timelineCases.voteInPoll(status, choices)
.observeOn(AndroidSchedulers.mainThread())
.autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))
.subscribe({poll ->
val viewData = ViewDataUtils.statusToViewData(
status,
alwaysShowSensitiveMedia
)
val newViewData = StatusViewData.Builder(viewData)
.setPoll(poll)
.createStatusViewData()
searchAdapter.updateStatusAtPosition(newViewData, position)
}, { t -> Log.d(TAG, "Failed to vote in poll " + status.id, t) })
}
}
companion object {
const val TAG = "SearchFragment"
}
}

View file

@ -45,6 +45,7 @@ import com.keylesspalace.tusky.appstore.UnfollowEvent;
import com.keylesspalace.tusky.db.AccountManager;
import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Filter;
import com.keylesspalace.tusky.entity.Poll;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
@ -620,6 +621,34 @@ public class TimelineFragment extends SFragment implements
updateAdapter();
}
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
final Status status = statuses.get(position).asRight();
setVoteForPoll(position, status, status.getPoll().votedCopy(choices));
timelineCases.voteInPoll(status, choices)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this)))
.subscribe(
(newPoll) -> setVoteForPoll(position, status, newPoll),
(t) -> Log.d(TAG,
"Failed to vote in poll: " + status.getId(), t)
);
}
private void setVoteForPoll(int position, Status status, Poll newPoll) {
Pair<StatusViewData.Concrete, Integer> actual =
findStatusAndPosition(position, status);
if (actual == null) return;
StatusViewData newViewData = new StatusViewData
.Builder(actual.first)
.setPoll(newPoll)
.createStatusViewData();
statuses.setPairedItem(actual.second, newViewData);
updateAdapter();
}
@Override
public void onMore(@NonNull View view, final int position) {
super.more(statuses.get(position).asRight(), view, position);

View file

@ -42,6 +42,7 @@ import com.keylesspalace.tusky.appstore.StatusComposedEvent;
import com.keylesspalace.tusky.appstore.StatusDeletedEvent;
import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Poll;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.StatusContext;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -393,6 +394,33 @@ public final class ViewThreadFragment extends SFragment implements
adapter.setStatuses(statuses.getPairedCopy());
}
public void onVoteInPoll(int position, @NonNull List<Integer> choices) {
final Status status = statuses.get(position).getActionableStatus();
setVoteForPoll(position, status.getPoll().votedCopy(choices));
timelineCases.voteInPoll(status, choices)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this)))
.subscribe(
(newPoll) -> setVoteForPoll(position, newPoll),
(t) -> Log.d(TAG,
"Failed to vote in poll: " + status.getId(), t)
);
}
private void setVoteForPoll(int position, Poll newPoll) {
StatusViewData.Concrete viewData = statuses.getPairedItem(position);
StatusViewData.Concrete newViewData = new StatusViewData.Builder(viewData)
.setPoll(newPoll)
.createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, true);
}
private void removeAllByAccountId(String accountId) {
Status status = null;
if (!statuses.isEmpty()) {