From 025ba68df4b831adc1bc36871fa021767a0822f6 Mon Sep 17 00:00:00 2001 From: kyori19 Date: Mon, 7 Sep 2020 22:57:59 +0900 Subject: [PATCH] Refactor quick compose * Stash visibility while composing reply * Remove announcements --- .../com/keylesspalace/tusky/MainActivity.kt | 19 +- .../tusky/ModalTimelineActivity.kt | 19 +- .../keylesspalace/tusky/StatusListActivity.kt | 20 +- .../keylesspalace/tusky/ViewTagActivity.java | 14 +- .../keylesspalace/tusky/appstore/Events.kt | 1 - .../tusky/di/ViewModelFactory.kt | 8 +- .../tusky/entity/Announcement.kt | 27 -- .../tusky/network/MastodonApi.kt | 3 - .../java/net/accelf/yuito/FooterDrawerItem.kt | 30 +- .../net/accelf/yuito/QuickTootHelper.java | 346 ------------------ .../java/net/accelf/yuito/QuickTootView.kt | 98 +++++ .../net/accelf/yuito/QuickTootViewModel.kt | 91 +++++ .../accelf/yuito/VisibilityToggleButton.kt | 59 +++ .../main/res/drawable/ic_arrow_drop_down.xml | 9 - .../main/res/drawable/ic_arrow_drop_up.xml | 9 - app/src/main/res/drawable/ic_chevron_left.xml | 9 - .../main/res/drawable/ic_chevron_right.xml | 9 - app/src/main/res/layout/activity_main.xml | 5 +- .../res/layout/activity_modal_timeline.xml | 7 +- .../main/res/layout/activity_statuslist.xml | 7 +- app/src/main/res/layout/activity_view_tag.xml | 7 +- app/src/main/res/layout/view_quick_toot.xml | 91 +---- app/src/main/res/values/strings.xml | 1 + 23 files changed, 326 insertions(+), 563 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt delete mode 100644 app/src/main/java/net/accelf/yuito/QuickTootHelper.java create mode 100644 app/src/main/java/net/accelf/yuito/QuickTootView.kt create mode 100644 app/src/main/java/net/accelf/yuito/QuickTootViewModel.kt create mode 100644 app/src/main/java/net/accelf/yuito/VisibilityToggleButton.kt delete mode 100644 app/src/main/res/drawable/ic_arrow_drop_down.xml delete mode 100644 app/src/main/res/drawable/ic_arrow_drop_up.xml delete mode 100644 app/src/main/res/drawable/ic_chevron_left.xml delete mode 100644 app/src/main/res/drawable/ic_chevron_right.xml diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index eef52e8fd..745acedb6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -34,10 +34,10 @@ import android.view.MenuItem import android.view.View import android.view.WindowManager import android.widget.ImageView +import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.widget.PopupMenu -import androidx.constraintlayout.widget.ConstraintLayout import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -65,6 +65,7 @@ import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity import com.keylesspalace.tusky.components.search.SearchActivity import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.fragment.NotificationsFragment import com.keylesspalace.tusky.fragment.SFragment @@ -94,7 +95,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.activity_main.* import net.accelf.yuito.FooterDrawerItem -import net.accelf.yuito.QuickTootHelper +import net.accelf.yuito.QuickTootViewModel import javax.inject.Inject class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector { @@ -110,6 +111,11 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje @Inject lateinit var conversationRepository: ConversationsRepository + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val quickTootViewModel: QuickTootViewModel by viewModels { viewModelFactory } + private lateinit var header: AccountHeaderView private var streamingTabsCount = 0 @@ -182,10 +188,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own setContentView(R.layout.activity_main) - val quickTootHelper = QuickTootHelper(this, quickTootContainer as ConstraintLayout?, accountManager, eventHub) - composeButton.setOnClickListener { - quickTootHelper.composeButton() - } + viewQuickToot.attachViewModel(quickTootViewModel, this) + composeButton.setOnClickListener(viewQuickToot::onFABClicked) val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false) mainToolbar.visible(!hideTopToolbar) @@ -238,7 +242,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje is ProfileEditedEvent -> onFetchUserInfoSuccess(event.newProfileData) is MainTabsChangedEvent -> setupTabs(false) } - quickTootHelper.handleEvent(event) + viewQuickToot.handleEvent(event) } // Flush old media that was cached for sharing @@ -475,7 +479,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje ) addStickyDrawerItems( FooterDrawerItem().apply { - eventHub = this@MainActivity.eventHub setSubscribeProxy( mastodonApi.getInstance() .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index fc42080a6..a43e2d080 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -4,10 +4,11 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem -import androidx.constraintlayout.widget.ConstraintLayout +import androidx.activity.viewModels import androidx.lifecycle.Lifecycle import com.google.android.material.floatingactionbutton.FloatingActionButton import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.uber.autodispose.AutoDispose.autoDisposable @@ -15,8 +16,10 @@ import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.synthetic.main.activity_main.viewQuickToot +import kotlinx.android.synthetic.main.activity_modal_timeline.* import kotlinx.android.synthetic.main.toolbar_basic.* -import net.accelf.yuito.QuickTootHelper +import net.accelf.yuito.QuickTootViewModel import javax.inject.Inject class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInjector { @@ -40,6 +43,10 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var eventHub: EventHub + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val quickTootViewModel: QuickTootViewModel by viewModels { viewModelFactory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,15 +69,13 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn .commit() } - val quickTootContainer = findViewById(R.id.quick_toot_container) - val composeButton = findViewById(R.id.floating_btn) - val quickTootHelper = QuickTootHelper(this, quickTootContainer, accountManager, eventHub) + viewQuickToot.attachViewModel(quickTootViewModel, this) eventHub.events .observeOn(AndroidSchedulers.mainThread()) .`as`(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) - .subscribe(quickTootHelper::handleEvent) - composeButton.setOnClickListener { quickTootHelper.composeButton() } + .subscribe(viewQuickToot::handleEvent) + floating_btn.setOnClickListener(viewQuickToot::onFABClicked) } override fun getActionButton(): FloatingActionButton? = null diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index dfc9da07a..c092a014a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -19,11 +19,11 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem -import androidx.constraintlayout.widget.ConstraintLayout +import androidx.activity.viewModels import androidx.fragment.app.commit import androidx.lifecycle.Lifecycle -import com.google.android.material.floatingactionbutton.FloatingActionButton import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.fragment.TimelineFragment import com.keylesspalace.tusky.fragment.TimelineFragment.Kind import com.uber.autodispose.AutoDispose @@ -33,8 +33,9 @@ import dagger.android.HasAndroidInjector import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.extensions.CacheImplementation import kotlinx.android.extensions.ContainerOptions +import kotlinx.android.synthetic.main.activity_statuslist.* import kotlinx.android.synthetic.main.toolbar_basic.* -import net.accelf.yuito.QuickTootHelper +import net.accelf.yuito.QuickTootViewModel import javax.inject.Inject class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { @@ -43,6 +44,10 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector @Inject lateinit var eventHub: EventHub + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val quickTootViewModel: QuickTootViewModel by viewModels{ viewModelFactory } private val kind: Kind get() = Kind.valueOf(intent.getStringExtra(EXTRA_KIND)!!) @@ -71,16 +76,13 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { replace(R.id.fragment_container, fragment) } - val quickTootContainer = findViewById(R.id.quick_toot_container) - val composeButton = findViewById(R.id.floating_btn) - val quickTootHelper = QuickTootHelper(this, quickTootContainer, accountManager, eventHub) + viewQuickToot.attachViewModel(quickTootViewModel, this) eventHub.events .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))) - .subscribe(quickTootHelper::handleEvent) - composeButton.setOnClickListener { quickTootHelper.composeButton() } - + .subscribe(viewQuickToot::handleEvent) + floating_btn.setOnClickListener(viewQuickToot::onFABClicked) } override fun onOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index 236540bbf..b66908b7c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -23,15 +23,16 @@ import android.view.MenuItem; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; -import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.Lifecycle; import com.keylesspalace.tusky.appstore.EventHub; +import com.keylesspalace.tusky.di.ViewModelFactory; import com.keylesspalace.tusky.fragment.TimelineFragment; -import net.accelf.yuito.QuickTootHelper; +import net.accelf.yuito.QuickTootView; +import net.accelf.yuito.QuickTootViewModel; import java.util.Collections; @@ -53,6 +54,8 @@ public class ViewTagActivity extends BottomSheetActivity implements HasAndroidIn public DispatchingAndroidInjector dispatchingAndroidInjector; @Inject public EventHub eventHub; + @Inject + public ViewModelFactory viewModelFactory; public static Intent getIntent(Context context, String tag){ Intent intent = new Intent(context,ViewTagActivity.class); @@ -82,13 +85,14 @@ public class ViewTagActivity extends BottomSheetActivity implements HasAndroidIn fragmentTransaction.replace(R.id.fragment_container, fragment); fragmentTransaction.commit(); - ConstraintLayout quickTootContainer = findViewById(R.id.quick_toot_container); - QuickTootHelper quickTootHelper = new QuickTootHelper(this, quickTootContainer, accountManager, eventHub); + QuickTootViewModel quickTootViewModel = viewModelFactory.create(QuickTootViewModel.class); + QuickTootView quickTootView = findViewById(R.id.viewQuickToot); + quickTootView.attachViewModel(quickTootViewModel, this); eventHub.getEvents() .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) - .subscribe(quickTootHelper::handleEvent); + .subscribe(quickTootView::handleEvent); } @Override diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt index 7be6c7deb..adef66195 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt @@ -23,4 +23,3 @@ data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable data class DomainMuteEvent(val instance: String): Dispatchable data class QuickReplyEvent(val status: Status) : Dispatchable data class StreamUpdateEvent(val status: Status, val targetKind: TimelineFragment.Kind, val targetIdentifier: String?, val first: Boolean) : Dispatchable -data class DrawerFooterClickedEvent(val placeholder: Boolean) : Dispatchable \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index f49294639..b6a5b98a2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -17,6 +17,7 @@ import dagger.Binds import dagger.MapKey import dagger.Module import dagger.multibindings.IntoMap +import net.accelf.yuito.QuickTootViewModel import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton @@ -85,5 +86,10 @@ abstract class ViewModelModule { @ViewModelKey(ScheduledTootViewModel::class) internal abstract fun scheduledTootViewModel(viewModel: ScheduledTootViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(QuickTootViewModel::class) + internal abstract fun quickTootViewModel(viewModel: QuickTootViewModel): ViewModel + //Add more ViewModels here -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt deleted file mode 100644 index 2b3a2ac18..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.keylesspalace.tusky.entity - -import android.text.Spanned -import com.google.gson.annotations.SerializedName -import java.util.* - -data class Announcement( - val id: String, - val content: Spanned, - @SerializedName("starts_at") val startsAt: Date, - @SerializedName("ends_at") val endsAt: Date, - @SerializedName("all_day") val allDay: Boolean, - val emojis: List, - val mentions: Array -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || javaClass != other.javaClass) return false - - val announcement = other as Announcement? - return id == announcement?.id - } - - override fun hashCode(): Int { - return id.hashCode() - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 0d89045db..767fe257a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -505,9 +505,6 @@ interface MastodonApi { @Field("choices[]") choices: List ): Single - @GET("api/v1/announcements") - fun listAnnouncements(): Single> - @POST("api/v1/accounts/{id}/block") fun blockAccountObservable( @Path("id") accountId: String diff --git a/app/src/main/java/net/accelf/yuito/FooterDrawerItem.kt b/app/src/main/java/net/accelf/yuito/FooterDrawerItem.kt index 1c5f93f8e..977c76a7f 100644 --- a/app/src/main/java/net/accelf/yuito/FooterDrawerItem.kt +++ b/app/src/main/java/net/accelf/yuito/FooterDrawerItem.kt @@ -5,20 +5,15 @@ import android.view.View import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.appstore.DrawerFooterClickedEvent -import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.entity.Instance import com.mikepenz.materialdrawer.model.AbstractDrawerItem import com.uber.autodispose.SingleSubscribeProxy +import kotlinx.android.synthetic.main.item_drawer_footer.view.* class FooterDrawerItem : AbstractDrawerItem() { - override val type: Int - get() = R.id.instanceData + override val type = R.id.instanceData - override val layoutRes: Int - get() = R.layout.item_drawer_footer - - lateinit var eventHub: EventHub + override val layoutRes = R.layout.item_drawer_footer private lateinit var context: Context private lateinit var instanceData: TextView @@ -29,24 +24,9 @@ class FooterDrawerItem : AbstractDrawerItem - var result = true - var text = instanceData.text.toString() - text += "?" - if (text.endsWith("???????")) { - text = text.substring(0, text.length - 7) - eventHub.dispatch(DrawerFooterClickedEvent(true)) - isExpanded = false - result = false - } - instanceData.text = text - result - } } - override fun getViewHolder(v: View): ViewHolder { - return ViewHolder(v) - } + override fun getViewHolder(v: View) = ViewHolder(v) fun setSubscribeProxy(subscribeProxy: SingleSubscribeProxy) { subscribeProxy.subscribe( @@ -60,6 +40,6 @@ class FooterDrawerItem : AbstractDrawerItem announcements; - - private static final String PREF_CURRENT_VISIBILITY = "current_visibility"; - - public QuickTootHelper(BottomSheetActivity activity, ConstraintLayout root, AccountManager accountManager, EventHub eventHub) { - context = root.getContext(); - quickReplyInfo = root.findViewById(R.id.quick_reply_info); - defaultTagInfo = root.findViewById(R.id.default_tag_info); - visibilityButton = root.findViewById(R.id.visibility_button); - tootEditText = root.findViewById(R.id.toot_edit_text); - openAnnouncementsButton = root.findViewById(R.id.button_open_announcements); - announcementsText = root.findViewById(R.id.text_view_announcements); - prevButton = root.findViewById(R.id.button_prev_announcements); - nextButton = root.findViewById(R.id.button_next_announcements); - announcementsCountText = root.findViewById(R.id.text_view_announcements_count); - quickTootButton = root.findViewById(R.id.toot_button); - - context = root.getContext(); - this.defPrefs = PreferenceManager.getDefaultSharedPreferences(context); - AccountEntity account = accountManager.getActiveAccount(); - if (account != null) { - domain = account.getDomain(); - loggedInUsername = account.getUsername(); - } - - this.eventHub = eventHub; - - updateVisibilityButton(); - updateDefaultTagInfo(); - visibilityButton.setOnClickListener(v -> setNextVisibility()); - quickTootButton.setOnClickListener(v -> quickToot()); - - listener = new LinkListener() { - @Override - public void onViewTag(String tag) { - context.startActivity(ViewTagActivity.getIntent(context, tag)); - } - - @Override - public void onViewAccount(String id) { - context.startActivity(AccountActivity.getIntent(context, id)); - } - - @Override - public void onViewUrl(String url, String text) { - activity.viewUrl(url, PostLookupFallbackBehavior.OPEN_IN_BROWSER, text); - } - }; - activity.mastodonApi.listAnnouncements() - .observeOn(AndroidSchedulers.mainThread()) - .as(autoDisposable(from(activity, Lifecycle.Event.ON_DESTROY))) - .subscribe( - a -> { - announcements = a; - updateAnnouncements(); - }, - Throwable::printStackTrace - ); - updateAnnouncements(); - openAnnouncementsButton.setOnClickListener(v -> toggleOpenAnnouncements()); - announcementsText.setOnClickListener(v -> toggleOpenAnnouncements()); - prevButton.setOnClickListener(v -> prevAnnouncement()); - nextButton.setOnClickListener(v -> nextAnnouncement()); - } - - public void composeButton() { - if (tootEditText.getText().length() == 0 && inReplyTo == null) { - context.startActivity(getComposeIntent(context, true, false)); - } else { - startComposeWithQuickComposeData(); - } - } - - public void handleEvent(Event event) { - if (event instanceof QuickReplyEvent) { - reply(((QuickReplyEvent) event).getStatus()); - } else if (event instanceof PreferenceChangedEvent) { - switch (((PreferenceChangedEvent) event).getPreferenceKey()) { - case PREF_CURRENT_VISIBILITY: { - updateVisibilityButton(); - break; - } - case PREF_DEFAULT_TAG: - case PREF_USE_DEFAULT_TAG: { - updateDefaultTagInfo(); - break; - } - } - } else if (event instanceof DrawerFooterClickedEvent) { - tootEditText.setText("にゃーん"); - } - } - - private void reply(Status status) { - inReplyTo = status; - updateQuickReplyInfo(); - } - - private void startComposeWithQuickComposeData() { - Intent intent = getComposeIntent(context, false, false); - resetQuickCompose(); - context.startActivity(intent); - } - - private void quickToot() { - if (tootEditText.getText().toString().length() > 0) { - Intent intent = getComposeIntent(context, false, true); - resetQuickCompose(); - context.startActivity(intent); - } - } - - private Intent getComposeIntent(Context context, boolean onlyVisibility, boolean tootRightNow) { - ComposeActivity.ComposeOptions options = new ComposeActivity.ComposeOptions(); - options.setVisibility(getCurrentVisibility()); - if (onlyVisibility) { - return ComposeActivity.startIntent(context, options); - } - options.setTootText(tootEditText.getText().toString()); - options.setTootRightNow(tootRightNow); - - if (inReplyTo != null) { - Status.Mention[] mentions = inReplyTo.getMentions(); - Set mentionedUsernames = new LinkedHashSet<>(); - mentionedUsernames.add(inReplyTo.getAccount().getUsername()); - for (Status.Mention mention : mentions) { - mentionedUsernames.add(mention.getUsername()); - } - mentionedUsernames.remove(loggedInUsername); - - options.setInReplyToId(inReplyTo.getId()); - options.setContentWarning(inReplyTo.getSpoilerText()); - options.setMentionedUsernames(mentionedUsernames); - options.setReplyingStatusAuthor(inReplyTo.getAccount().getLocalUsername()); - options.setReplyingStatusContent(inReplyTo.getContent().toString()); - } - - return ComposeActivity.startIntent(context, options); - } - - private void resetQuickCompose() { - tootEditText.getText().clear(); - inReplyTo = null; - updateQuickReplyInfo(); - } - - private void updateQuickReplyInfo() { - if (inReplyTo != null) { - quickReplyInfo.setText(String.format("Reply to : %s", inReplyTo.getAccount().getUsername())); - } else { - quickReplyInfo.setText(""); - } - } - - private void updateDefaultTagInfo() { - boolean useDefaultTag = defPrefs.getBoolean(PREF_USE_DEFAULT_TAG, false); - String defaultText = defPrefs.getString(PREF_DEFAULT_TAG, ""); - if (useDefaultTag) { - defaultTagInfo.setText(String.format("%s : %s", context.getString(R.string.hint_default_text), defaultText)); - defaultTagInfo.setTextColor(ThemeUtils.getColor(context, R.attr.colorInfo)); - } else { - defaultTagInfo.setText(String.format("%s inactive", context.getString(R.string.hint_default_text))); - defaultTagInfo.setTextColor(ThemeUtils.getColor(context, android.R.attr.textColorTertiary)); - } - } - - private Status.Visibility getCurrentVisibility() { - Status.Visibility visibility = Status.Visibility.byNum(defPrefs.getInt(PREF_CURRENT_VISIBILITY, Status.Visibility.PUBLIC.getNum())); - if (!Arrays.asList(CAN_USE_UNLEAKABLE) - .contains(domain) && visibility == Status.Visibility.UNLEAKABLE) { - defPrefs.edit() - .putInt(PREF_CURRENT_VISIBILITY, Status.Visibility.PUBLIC.getNum()) - .apply(); - eventHub.dispatch(new PreferenceChangedEvent(PREF_CURRENT_VISIBILITY)); - return Status.Visibility.PUBLIC; - } - return visibility; - } - - private void updateVisibilityButton() { - Status.Visibility visibility = getCurrentVisibility(); - quickTootButton.setStatusVisibility(visibility); - switch (visibility) { - case PUBLIC: - visibilityButton.setImageResource(R.drawable.ic_public_24dp); - break; - case UNLISTED: - visibilityButton.setImageResource(R.drawable.ic_lock_open_24dp); - break; - case PRIVATE: - visibilityButton.setImageResource(R.drawable.ic_lock_outline_24dp); - break; - case UNLEAKABLE: - visibilityButton.setImageResource(R.drawable.ic_low_vision_24dp); - break; - } - } - - private void setNextVisibility() { - Status.Visibility visibility = getCurrentVisibility(); - switch (visibility) { - case PUBLIC: - visibility = Status.Visibility.UNLISTED; - break; - case UNLISTED: - visibility = Status.Visibility.PRIVATE; - break; - case PRIVATE: - if (Arrays.asList(CAN_USE_UNLEAKABLE).contains(domain)) { - visibility = Status.Visibility.UNLEAKABLE; - } else { - visibility = Status.Visibility.PUBLIC; - } - break; - case UNLEAKABLE: - case UNKNOWN: - visibility = Status.Visibility.PUBLIC; - break; - } - defPrefs.edit() - .putInt(PREF_CURRENT_VISIBILITY, visibility.getNum()) - .apply(); - eventHub.dispatch(new PreferenceChangedEvent(PREF_CURRENT_VISIBILITY)); - updateVisibilityButton(); - } - - private void updateAnnouncements() { - if (ListUtils.isEmpty(announcements)) { - openAnnouncementsButton.setVisibility(View.GONE); - announcementsText.setVisibility(View.GONE); - announcementsCountText.setVisibility(View.GONE); - prevButton.setVisibility(View.GONE); - nextButton.setVisibility(View.GONE); - } else { - openAnnouncementsButton.setVisibility(View.VISIBLE); - announcementsText.setVisibility(View.VISIBLE); - announcementsCountText.setVisibility(View.VISIBLE); - if (open) { - prevButton.setVisibility(View.VISIBLE); - nextButton.setVisibility(View.VISIBLE); - } else { - prevButton.setVisibility(View.GONE); - nextButton.setVisibility(View.GONE); - } - - openAnnouncementsButton.setImageDrawable(ContextCompat.getDrawable(context, open ? R.drawable.ic_arrow_drop_down : R.drawable.ic_arrow_drop_up)); - announcementsText.setSingleLine(!open); - announcementsCountText.setText(String.format(Locale.getDefault(), "(%d/%d)", index + 1, announcements.size())); - Announcement announcement = announcements.get(index); - LinkHelper.setClickableText(announcementsText, announcement.getContent(), announcement.getMentions(), listener, false); - } - } - - private void toggleOpenAnnouncements() { - open = !open; - updateAnnouncements(); - } - - private void prevAnnouncement() { - if (index > 0) { - index--; - updateAnnouncements(); - } - } - - private void nextAnnouncement() { - if (index < announcements.size() - 1) { - index++; - updateAnnouncements(); - } - } -} diff --git a/app/src/main/java/net/accelf/yuito/QuickTootView.kt b/app/src/main/java/net/accelf/yuito/QuickTootView.kt new file mode 100644 index 000000000..9e5950198 --- /dev/null +++ b/app/src/main/java/net/accelf/yuito/QuickTootView.kt @@ -0,0 +1,98 @@ +package net.accelf.yuito + +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.observe +import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.appstore.Event +import com.keylesspalace.tusky.appstore.PreferenceChangedEvent +import com.keylesspalace.tusky.appstore.QuickReplyEvent +import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.PREF_DEFAULT_TAG +import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.PREF_USE_DEFAULT_TAG +import com.keylesspalace.tusky.util.ThemeUtils +import kotlinx.android.synthetic.main.view_quick_toot.view.* + +class QuickTootView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + ConstraintLayout(context, attrs) { + + private val preference by lazy { PreferenceManager.getDefaultSharedPreferences(context) } + + private lateinit var viewModel: QuickTootViewModel + + init { + LayoutInflater.from(context).inflate(R.layout.view_quick_toot, this, true) + } + + fun attachViewModel(viewModel: QuickTootViewModel, owner: LifecycleOwner) { + this.viewModel = viewModel + + buttonVisibility.attachViewModel(viewModel, owner) + + viewModel.content.observe(owner) { + if (editTextContent.text.toString() != it) { + editTextContent.setText(it) + } + } + editTextContent.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + viewModel.content.value = s.toString() + } + }) + + viewModel.inReplyTo.observe(owner) { + textQuickReply.text = it?.let { "Reply to ${it.account.username}" } ?: "" + } + + viewModel.defaultTag.observe(owner) { + textDefaultTag.text = it?.let { "${context.getString(R.string.hint_default_text)} : $it" } + ?: "${context.getString(R.string.hint_default_text)} inactive" + textDefaultTag.setTextColor(ThemeUtils.getColor(context, it?.let { R.attr.colorInfo } + ?: android.R.attr.textColorTertiary)) + } + syncDefaultTag() + + viewModel.visibility.observe(owner) { + buttonToot.setStatusVisibility(it) + } + buttonToot.setOnClickListener { + val intent = ComposeActivity.startIntent(it.context, viewModel.composeOptions(true)) + viewModel.reset() + it.context.startActivity(intent) + } + } + + private fun syncDefaultTag() { + viewModel.defaultTag.value = if (preference.getBoolean(PREF_USE_DEFAULT_TAG, false)) { + preference.getString(PREF_DEFAULT_TAG, null) + } else { + null + } + } + + fun onFABClicked(view: View) { + val intent = ComposeActivity.startIntent(view.context, viewModel.composeOptions(false)) + viewModel.reset() + view.context.startActivity(intent) + } + + fun handleEvent(event: Event?) { + when (event) { + is QuickReplyEvent -> viewModel.reply(event) + is PreferenceChangedEvent -> { + if (event.preferenceKey in arrayOf(PREF_DEFAULT_TAG, PREF_USE_DEFAULT_TAG)) { + syncDefaultTag() + } + } + } + } +} diff --git a/app/src/main/java/net/accelf/yuito/QuickTootViewModel.kt b/app/src/main/java/net/accelf/yuito/QuickTootViewModel.kt new file mode 100644 index 000000000..adb4efa7f --- /dev/null +++ b/app/src/main/java/net/accelf/yuito/QuickTootViewModel.kt @@ -0,0 +1,91 @@ +package net.accelf.yuito + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.keylesspalace.tusky.appstore.QuickReplyEvent +import com.keylesspalace.tusky.components.compose.ComposeActivity +import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.CAN_USE_UNLEAKABLE +import com.keylesspalace.tusky.components.compose.mutableLiveData +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.entity.Status.Visibility +import javax.inject.Inject + +class QuickTootViewModel @Inject constructor( + accountManager: AccountManager +): ViewModel() { + + private val account = accountManager.activeAccount!! + + private val unleakableAllowed by lazy { CAN_USE_UNLEAKABLE.contains(account.domain) } + + val content = mutableLiveData("") + + private val visibilityMutable = mutableLiveData(Visibility.PUBLIC) + val visibility: LiveData = visibilityMutable + private var stashedVisibility: Visibility? = null + + private val inReplyToMutable: MutableLiveData = mutableLiveData(null) + val inReplyTo: LiveData = inReplyToMutable + + val defaultTag: MutableLiveData = mutableLiveData(null) + + fun setInitialVisibility(num: Int) { + visibilityMutable.value = (Visibility.byNum(num) + .takeUnless { it == Visibility.UNKNOWN } + ?: account.defaultPostPrivacy) + .takeUnless { it == Visibility.UNLEAKABLE && unleakableAllowed } + ?: Visibility.PRIVATE + } + + fun stepVisibility() { + visibilityMutable.value = when (visibility.value) { + Visibility.PUBLIC -> Visibility.UNLISTED + Visibility.UNLISTED -> Visibility.PRIVATE + Visibility.PRIVATE -> when (unleakableAllowed) { + true -> Visibility.UNLEAKABLE + false -> Visibility.PUBLIC + } + Visibility.UNLEAKABLE -> Visibility.PUBLIC + else -> Visibility.PUBLIC + } + } + + private fun overrideVisibility(overrideTo: Visibility) { + stashedVisibility = visibility.value + visibilityMutable.value = overrideTo + } + + fun reply(event: QuickReplyEvent) { + val status = event.status.actionableStatus + inReplyToMutable.value = status + overrideVisibility(status.visibility) + } + + fun composeOptions(tootRightNow: Boolean): ComposeActivity.ComposeOptions { + return ComposeActivity.ComposeOptions( + tootText = content.value, + mentionedUsernames = inReplyTo.value + ?.let { + linkedSetOf(it.account.username, *(it.mentions.map { mention -> mention.username }.toTypedArray())) + .apply { remove(account.username) } + }, + inReplyToId = inReplyTo.value?.id, + visibility = visibility.value, + contentWarning = inReplyTo.value?.spoilerText, + replyingStatusAuthor = inReplyTo.value?.account?.name, + replyingStatusContent = inReplyTo.value?.content?.toString(), + tootRightNow = tootRightNow + ) + } + + fun reset() { + content.value = "" + inReplyToMutable.value = null + stashedVisibility?.let { + visibilityMutable.value = stashedVisibility + stashedVisibility = null + } + } +} diff --git a/app/src/main/java/net/accelf/yuito/VisibilityToggleButton.kt b/app/src/main/java/net/accelf/yuito/VisibilityToggleButton.kt new file mode 100644 index 000000000..5ddd71466 --- /dev/null +++ b/app/src/main/java/net/accelf/yuito/VisibilityToggleButton.kt @@ -0,0 +1,59 @@ +package net.accelf.yuito + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.observe +import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.entity.Status.Visibility + +class VisibilityToggleButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + AppCompatImageView(context, attrs) { + + private val preference by lazy { PreferenceManager.getDefaultSharedPreferences(context) } + + init { + isClickable = true + isFocusable = true + } + + fun attachViewModel(viewModel: QuickTootViewModel, owner: LifecycleOwner) { + viewModel.visibility.observe(owner, ::updateVisibility) + viewModel.setInitialVisibility(preference.getInt(PREF_CURRENT_VISIBILITY, Visibility.UNKNOWN.num)) + setOnClickListener{ viewModel.stepVisibility() } + } + + private fun updateVisibility(visibility: Visibility) { + setImageResource( + when (visibility) { + Visibility.PUBLIC -> R.drawable.ic_public_24dp + Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp + Visibility.PRIVATE -> R.drawable.ic_lock_outline_24dp + Visibility.DIRECT -> R.drawable.ic_email_24dp + Visibility.UNLEAKABLE -> R.drawable.ic_low_vision_24dp + else -> R.drawable.ic_lock_open_24dp + } + ) + + contentDescription = context.getString( + when (visibility) { + Visibility.UNKNOWN -> R.string.visibility_unknown + Visibility.PUBLIC -> R.string.visibility_public + Visibility.UNLISTED -> R.string.visibility_unlisted + Visibility.PRIVATE -> R.string.visibility_private + Visibility.DIRECT -> R.string.visibility_direct + Visibility.UNLEAKABLE -> R.string.visibility_unleakable + } + ) + + preference.edit() + .putInt(PREF_CURRENT_VISIBILITY, visibility.num) + .apply() + } + + companion object { + private const val PREF_CURRENT_VISIBILITY = "current_visibility" + } +} diff --git a/app/src/main/res/drawable/ic_arrow_drop_down.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml deleted file mode 100644 index 62b27ef0b..000000000 --- a/app/src/main/res/drawable/ic_arrow_drop_down.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_arrow_drop_up.xml b/app/src/main/res/drawable/ic_arrow_drop_up.xml deleted file mode 100644 index b1442ce15..000000000 --- a/app/src/main/res/drawable/ic_arrow_drop_up.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_chevron_left.xml b/app/src/main/res/drawable/ic_chevron_left.xml deleted file mode 100644 index e6bb3ca92..000000000 --- a/app/src/main/res/drawable/ic_chevron_left.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml deleted file mode 100644 index 24835127d..000000000 --- a/app/src/main/res/drawable/ic_chevron_right.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 73f0971b0..3911c5785 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -81,9 +81,8 @@ - diff --git a/app/src/main/res/layout/activity_modal_timeline.xml b/app/src/main/res/layout/activity_modal_timeline.xml index 2a956cf66..485ed5923 100644 --- a/app/src/main/res/layout/activity_modal_timeline.xml +++ b/app/src/main/res/layout/activity_modal_timeline.xml @@ -35,11 +35,10 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_statuslist.xml b/app/src/main/res/layout/activity_statuslist.xml index 4b805f604..68f722508 100644 --- a/app/src/main/res/layout/activity_statuslist.xml +++ b/app/src/main/res/layout/activity_statuslist.xml @@ -35,11 +35,10 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_view_tag.xml b/app/src/main/res/layout/activity_view_tag.xml index d48f3e7f9..82efe7691 100644 --- a/app/src/main/res/layout/activity_view_tag.xml +++ b/app/src/main/res/layout/activity_view_tag.xml @@ -25,11 +25,10 @@ - - \ No newline at end of file + diff --git a/app/src/main/res/layout/view_quick_toot.xml b/app/src/main/res/layout/view_quick_toot.xml index 6c35b450a..323c7d15f 100644 --- a/app/src/main/res/layout/view_quick_toot.xml +++ b/app/src/main/res/layout/view_quick_toot.xml @@ -5,101 +5,32 @@ android:layout_height="match_parent" android:background="?attr/colorSurface"> - - - - - - - - - - - - + app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintEnd_toStartOf="@id/buttonToot" + app:layout_constraintStart_toEndOf="@id/buttonVisibility" /> Followers-Only: Post to followers only Unleakable: Post to followed user by you only. Direct: Post to mentioned users only + Unknown visibility Notifications Notifications