List tabs (#1570)
* List tabs * Add comment * Move decoder part to TabData.createTabDataFromId * Fix drawable tint * Use dedicated view for list picker * Error log * Fix logging Co-Authored-By: Konrad Pozniak <connyduck@users.noreply.github.com> * Fix tint color Co-Authored-By: Konrad Pozniak <connyduck@users.noreply.github.com> * Fix missing import * Move encoding part too * Fix comment * Fix decoder * Revert "Fix decoder" This reverts commit fdc45aac9c113348f8740e6692d2d8e9ace14f7f. * Revert "Fix comment" This reverts commit 704b4e6d2e5545d5f2d20c9bc6bc276d6257d119. * Revert "Move encoding part too" This reverts commit 32e77346ff98ae1133e76ab6dfd880b26171005e. * Revert "Move decoder part to TabData.createTabDataFromId" This reverts commit d1cd2070ab564d2e33874225272a71f0904d681e.
This commit is contained in:
parent
bbeb46adaf
commit
d6ae071a09
9 changed files with 125 additions and 5 deletions
|
@ -478,8 +478,12 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
|
||||||
tabLayout.removeAllTabs();
|
tabLayout.removeAllTabs();
|
||||||
for (int i = 0; i < tabs.size(); i++) {
|
for (int i = 0; i < tabs.size(); i++) {
|
||||||
TabLayout.Tab tab = tabLayout.newTab()
|
TabLayout.Tab tab = tabLayout.newTab()
|
||||||
.setIcon(tabs.get(i).getIcon())
|
.setIcon(tabs.get(i).getIcon());
|
||||||
.setContentDescription(tabs.get(i).getText());
|
if (tabs.get(i).getId().equals(TabDataKt.LIST)) {
|
||||||
|
tab.setContentDescription(tabs.get(i).getArguments().get(1));
|
||||||
|
} else {
|
||||||
|
tab.setContentDescription(tabs.get(i).getText());
|
||||||
|
}
|
||||||
tabLayout.addTab(tab);
|
tabLayout.addTab(tab);
|
||||||
if (tabs.get(i).getId().equals(TabDataKt.NOTIFICATIONS)) {
|
if (tabs.get(i).getId().equals(TabDataKt.NOTIFICATIONS)) {
|
||||||
notificationTabPosition = i;
|
notificationTabPosition = i;
|
||||||
|
|
|
@ -30,6 +30,7 @@ const val LOCAL = "Local"
|
||||||
const val FEDERATED = "Federated"
|
const val FEDERATED = "Federated"
|
||||||
const val DIRECT = "Direct"
|
const val DIRECT = "Direct"
|
||||||
const val HASHTAG = "Hashtag"
|
const val HASHTAG = "Hashtag"
|
||||||
|
const val LIST = "List"
|
||||||
|
|
||||||
data class TabData(val id: String,
|
data class TabData(val id: String,
|
||||||
@StringRes val text: Int,
|
@StringRes val text: Int,
|
||||||
|
@ -45,6 +46,7 @@ fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabD
|
||||||
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) })
|
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) })
|
||||||
DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.reblog_direct_dark, { ConversationsFragment.newInstance() })
|
DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.reblog_direct_dark, { ConversationsFragment.newInstance() })
|
||||||
HASHTAG -> TabData(HASHTAG, R.string.hashtag, R.drawable.ic_hashtag, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.TAG, args.getOrNull(0).orEmpty()) }, arguments)
|
HASHTAG -> TabData(HASHTAG, R.string.hashtag, R.drawable.ic_hashtag, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.TAG, args.getOrNull(0).orEmpty()) }, arguments)
|
||||||
|
LIST -> TabData(LIST, R.string.list, R.drawable.ic_list, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, arguments)
|
||||||
else -> throw IllegalArgumentException("unknown tab type")
|
else -> throw IllegalArgumentException("unknown tab type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package com.keylesspalace.tusky
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.AppCompatEditText
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
|
@ -25,15 +26,18 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.keylesspalace.tusky.adapter.ItemInteractionListener
|
import com.keylesspalace.tusky.adapter.ItemInteractionListener
|
||||||
|
import com.keylesspalace.tusky.adapter.ListSelectionAdapter
|
||||||
import com.keylesspalace.tusky.adapter.TabAdapter
|
import com.keylesspalace.tusky.adapter.TabAdapter
|
||||||
import com.keylesspalace.tusky.appstore.EventHub
|
import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.util.onTextChanged
|
import com.keylesspalace.tusky.util.onTextChanged
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import com.uber.autodispose.autoDispose
|
import com.uber.autodispose.autoDispose
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.android.synthetic.main.activity_tab_preference.*
|
import kotlinx.android.synthetic.main.activity_tab_preference.*
|
||||||
import kotlinx.android.synthetic.main.toolbar_basic.*
|
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||||
|
@ -42,6 +46,8 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener {
|
class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListener {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var mastodonApi: MastodonApi
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var eventHub: EventHub
|
lateinit var eventHub: EventHub
|
||||||
|
|
||||||
|
@ -151,6 +157,11 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tab.id == LIST) {
|
||||||
|
showSelectListDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
currentTabs.add(tab)
|
currentTabs.add(tab)
|
||||||
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||||
updateAvailableTabs()
|
updateAvailableTabs()
|
||||||
|
@ -200,6 +211,33 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
editText.requestFocus()
|
editText.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSelectListDialog() {
|
||||||
|
val adapter = ListSelectionAdapter(this)
|
||||||
|
mastodonApi.getLists()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
|
.subscribe (
|
||||||
|
{ lists ->
|
||||||
|
adapter.addAll(lists)
|
||||||
|
},
|
||||||
|
{ throwable ->
|
||||||
|
Log.e("TabPreferenceActivity", "failed to load lists", throwable)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.select_list_title)
|
||||||
|
.setAdapter(adapter) { _, position ->
|
||||||
|
val list = adapter.getItem(position)
|
||||||
|
val newTab = createTabDataFromId(LIST, listOf(list!!.id, list.title))
|
||||||
|
currentTabs.add(newTab)
|
||||||
|
currentTabsAdapter.notifyItemInserted(currentTabs.size - 1)
|
||||||
|
updateAvailableTabs()
|
||||||
|
saveTabs()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private fun validateHashtag(input: CharSequence?): Boolean {
|
private fun validateHashtag(input: CharSequence?): Boolean {
|
||||||
val trimmedInput = input?.trim() ?: ""
|
val trimmedInput = input?.trim() ?: ""
|
||||||
return trimmedInput.isNotEmpty() && hashtagRegex.matcher(trimmedInput).matches()
|
return trimmedInput.isNotEmpty() && hashtagRegex.matcher(trimmedInput).matches()
|
||||||
|
@ -230,6 +268,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
}
|
}
|
||||||
|
|
||||||
addableTabs.add(createTabDataFromId(HASHTAG))
|
addableTabs.add(createTabDataFromId(HASHTAG))
|
||||||
|
addableTabs.add(createTabDataFromId(LIST))
|
||||||
|
|
||||||
addTabAdapter.updateData(addableTabs)
|
addTabAdapter.updateData(addableTabs)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Copyright 2019 kyori19
|
||||||
|
*
|
||||||
|
* 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.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.entity.MastoList
|
||||||
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
|
import kotlinx.android.synthetic.main.item_picker_list.view.*
|
||||||
|
|
||||||
|
class ListSelectionAdapter(context: Context) : ArrayAdapter<MastoList>(context, R.layout.item_autocomplete_hashtag) {
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var view = convertView
|
||||||
|
|
||||||
|
if (convertView == null) {
|
||||||
|
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||||
|
view = layoutInflater.inflate(R.layout.item_picker_list, parent, false)
|
||||||
|
}
|
||||||
|
view!!
|
||||||
|
|
||||||
|
val list = getItem(position)
|
||||||
|
if (list != null) {
|
||||||
|
val title = view.title
|
||||||
|
title.text = list.title
|
||||||
|
val icon = ThemeUtils.getTintedDrawable(context, R.drawable.ic_list, android.R.attr.textColorTertiary)
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.keylesspalace.tusky.HASHTAG
|
import com.keylesspalace.tusky.HASHTAG
|
||||||
|
import com.keylesspalace.tusky.LIST
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.TabData
|
import com.keylesspalace.tusky.TabData
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
|
@ -57,7 +58,11 @@ class TabAdapter(private var data: List<TabData>,
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val context = holder.itemView.context
|
val context = holder.itemView.context
|
||||||
holder.itemView.textView.setText(data[position].text)
|
if (!small && data[position].id == LIST) {
|
||||||
|
holder.itemView.textView.text = data[position].arguments.getOrNull(1).orEmpty()
|
||||||
|
} else {
|
||||||
|
holder.itemView.textView.setText(data[position].text)
|
||||||
|
}
|
||||||
val iconDrawable = ThemeUtils.getTintedDrawable(context, data[position].icon, android.R.attr.textColorSecondary)
|
val iconDrawable = ThemeUtils.getTintedDrawable(context, data[position].icon, android.R.attr.textColorSecondary)
|
||||||
holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconDrawable, null, null, null)
|
holder.itemView.textView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconDrawable, null, null, null)
|
||||||
if (small) {
|
if (small) {
|
||||||
|
|
|
@ -28,6 +28,8 @@ import com.keylesspalace.tusky.entity.Poll
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||||
import com.keylesspalace.tusky.util.HtmlUtils
|
import com.keylesspalace.tusky.util.HtmlUtils
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
@ -61,13 +63,14 @@ class Converters {
|
||||||
return str?.split(";")
|
return str?.split(";")
|
||||||
?.map {
|
?.map {
|
||||||
val data = it.split(":")
|
val data = it.split(":")
|
||||||
createTabDataFromId(data[0], data.drop(1))
|
createTabDataFromId(data[0], data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun tabDataToString(tabData: List<TabData>?): String? {
|
fun tabDataToString(tabData: List<TabData>?): String? {
|
||||||
return tabData?.joinToString(";") { it.id + ":" + it.arguments.joinToString(":") }
|
// List name may include ":"
|
||||||
|
return tabData?.joinToString(";") { it.id + ":" + it.arguments.joinToString(":") { s -> URLEncoder.encode(s, "UTF-8") } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
|
|
9
app/src/main/res/drawable-anydpi/ic_list.xml
Normal file
9
app/src/main/res/drawable-anydpi/ic_list.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
|
||||||
|
</vector>
|
8
app/src/main/res/layout/item_picker_list.xml
Normal file
8
app/src/main/res/layout/item_picker_list.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:textSize="?attr/status_text_medium" />
|
|
@ -470,6 +470,8 @@
|
||||||
<string name="edit_hashtag_title">Edit hashtag</string>
|
<string name="edit_hashtag_title">Edit hashtag</string>
|
||||||
<string name="edit_hashtag_hint">Hashtag without #</string>
|
<string name="edit_hashtag_hint">Hashtag without #</string>
|
||||||
<string name="hashtag">Hashtag</string>
|
<string name="hashtag">Hashtag</string>
|
||||||
|
<string name="select_list_title">Select list</string>
|
||||||
|
<string name="list">List</string>
|
||||||
<string name="notifications_clear">Clear</string>
|
<string name="notifications_clear">Clear</string>
|
||||||
<string name="notifications_apply_filter">Filter</string>
|
<string name="notifications_apply_filter">Filter</string>
|
||||||
<string name="filter_apply">Apply</string>
|
<string name="filter_apply">Apply</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue