diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java index 714539c23..170e83ae1 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java @@ -208,4 +208,5 @@ public interface IntentConstants { String EXTRA_API_CONFIG = "api_config"; String EXTRA_COUNT = "count"; String EXTRA_REQUEST_CODE = "request_code"; + String EXTRA_FROM_CACHE = "from_cache"; } diff --git a/twidere/src/main/AndroidManifest.xml b/twidere/src/main/AndroidManifest.xml index cb95b0446..8ad8ccb7e 100644 --- a/twidere/src/main/AndroidManifest.xml +++ b/twidere/src/main/AndroidManifest.xml @@ -377,8 +377,9 @@ + android:label="@string/select_user" + android:theme="@style/Theme.Twidere.Dialog" + android:windowSoftInputMode="adjustResize"> @@ -387,8 +388,9 @@ + android:label="@string/select_user_list" + android:theme="@style/Theme.Twidere.Dialog" + android:windowSoftInputMode="adjustResize"> diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserListSelectorActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserListSelectorActivity.kt index d2d7404df..20bc9d6f9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserListSelectorActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserListSelectorActivity.kt @@ -24,6 +24,7 @@ import android.content.Intent import android.os.AsyncTask import android.os.Bundle import android.support.v4.app.DialogFragment +import android.support.v4.app.LoaderManager import android.text.TextUtils import android.text.TextUtils.isEmpty import android.util.Log @@ -43,6 +44,7 @@ import org.mariotaku.twidere.adapter.UserAutoCompleteAdapter import org.mariotaku.twidere.constant.IntentConstants.* import org.mariotaku.twidere.fragment.CreateUserListDialogFragment import org.mariotaku.twidere.fragment.ProgressDialogFragment +import org.mariotaku.twidere.model.ParcelableUser import org.mariotaku.twidere.model.ParcelableUserList import org.mariotaku.twidere.model.SingleResponse import org.mariotaku.twidere.model.UserKey diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserSelectorActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserSelectorActivity.kt index 4e2f78a29..40c04606f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserSelectorActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/UserSelectorActivity.kt @@ -21,83 +21,63 @@ package org.mariotaku.twidere.activity import android.app.Activity import android.content.Intent -import android.os.AsyncTask import android.os.Bundle -import android.support.v4.app.DialogFragment +import android.support.v4.app.LoaderManager +import android.support.v4.content.Loader import android.text.TextUtils.isEmpty -import android.util.Log import android.view.View -import android.view.View.OnClickListener import android.widget.AdapterView import android.widget.AdapterView.OnItemClickListener import android.widget.ListView import kotlinx.android.synthetic.main.activity_user_selector.* -import org.mariotaku.microblog.library.MicroBlogException -import org.mariotaku.microblog.library.twitter.model.Paging +import kotlinx.android.synthetic.main.layout_list_with_empty_view.* +import org.mariotaku.ktextension.Bundle +import org.mariotaku.ktextension.isNotNullOrEmpty +import org.mariotaku.ktextension.set import org.mariotaku.twidere.R -import org.mariotaku.twidere.TwidereConstants.LOGTAG import org.mariotaku.twidere.adapter.SimpleParcelableUsersAdapter -import org.mariotaku.twidere.adapter.UserAutoCompleteAdapter import org.mariotaku.twidere.constant.IntentConstants.* -import org.mariotaku.twidere.fragment.CreateUserListDialogFragment -import org.mariotaku.twidere.fragment.ProgressDialogFragment +import org.mariotaku.twidere.loader.CacheUserSearchLoader import org.mariotaku.twidere.model.ParcelableUser -import org.mariotaku.twidere.model.SingleResponse import org.mariotaku.twidere.model.UserKey -import org.mariotaku.twidere.model.util.ParcelableUserUtils -import org.mariotaku.twidere.util.AsyncTaskUtils -import org.mariotaku.twidere.util.MicroBlogAPIFactory import org.mariotaku.twidere.util.ParseUtils -import java.util.* +import org.mariotaku.twidere.util.view.SimpleTextWatcher -class UserSelectorActivity : BaseActivity(), OnClickListener, OnItemClickListener { +class UserSelectorActivity : BaseActivity(), OnItemClickListener, LoaderManager.LoaderCallbacks> { private lateinit var usersAdapter: SimpleParcelableUsersAdapter - private var screenName: String? = null + private var loaderInitialized: Boolean = false + + private val accountKey: UserKey? + get() = intent.getParcelableExtra(EXTRA_ACCOUNT_KEY) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val intent = intent - if (!intent.hasExtra(EXTRA_ACCOUNT_KEY)) { + val accountKey = this.accountKey ?: run { finish() return } setContentView(R.layout.activity_user_selector) + + editScreenName.addTextChangedListener(object : SimpleTextWatcher() { + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + searchUser(accountKey, s.toString(), true) + } + }) + screenNameConfirm.setOnClickListener { + val screenName = ParseUtils.parseString(editScreenName.text) + searchUser(accountKey, screenName, false) + } + if (savedInstanceState == null) { - screenName = intent.getStringExtra(EXTRA_SCREEN_NAME) - } else { - screenName = savedInstanceState.getString(EXTRA_SCREEN_NAME) + editScreenName.setText(intent.getStringExtra(EXTRA_SCREEN_NAME)) } - - if (!isEmpty(screenName)) { - searchUser(screenName!!) - } - val adapter = UserAutoCompleteAdapter(this) - adapter.accountKey = accountKey - editScreenName.setAdapter(adapter) - editScreenName.setText(screenName) usersAdapter = SimpleParcelableUsersAdapter(this) - usersList.adapter = usersAdapter - usersList.onItemClickListener = this - screenNameConfirm.setOnClickListener(this) - } + listView.adapter = usersAdapter + listView.onItemClickListener = this - override fun onClick(v: View) { - when (v.id) { - R.id.screenNameConfirm -> { - val screen_name = ParseUtils.parseString(editScreenName.text) - if (isEmpty(screen_name)) return - searchUser(screen_name) - } - R.id.createList -> { - val f = CreateUserListDialogFragment() - val args = Bundle() - args.putParcelable(EXTRA_ACCOUNT_KEY, accountKey) - f.arguments = args - f.show(supportFragmentManager, null) - } - } + showSearchHint() } override fun onItemClick(view: AdapterView<*>, child: View, position: Int, id: Long) { @@ -110,93 +90,79 @@ class UserSelectorActivity : BaseActivity(), OnClickListener, OnItemClickListene finish() } - fun setUsersData(data: List) { + override fun onCreateLoader(id: Int, args: Bundle): Loader> { + val accountKey = args.getParcelable(EXTRA_ACCOUNT_KEY) + val query = args.getString(EXTRA_QUERY) + val fromCache = args.getBoolean(EXTRA_FROM_CACHE) + if (!fromCache) { + showProgress() + } + return CacheUserSearchLoader(this, accountKey, query, fromCache, true) + } + + override fun onLoaderReset(loader: Loader>) { + usersAdapter.setData(null, true) + } + + override fun onLoadFinished(loader: Loader>, data: List?) { + progressContainer.visibility = View.GONE + listContainer.visibility = View.VISIBLE usersAdapter.setData(data, true) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(EXTRA_SCREEN_NAME, screenName) - } - - private val accountKey: UserKey - get() = intent.getParcelableExtra(EXTRA_ACCOUNT_KEY) - - - private fun searchUser(name: String) { - val task = SearchUsersTask(this, accountKey, name) - AsyncTaskUtils.executeTask(task) - } - - private fun dismissDialogFragment(tag: String) { - executeAfterFragmentResumed { activity -> - val fm = activity.supportFragmentManager - val f = fm.findFragmentByTag(tag) - if (f is DialogFragment) { - f.dismiss() - } + loader as CacheUserSearchLoader + if (data.isNotNullOrEmpty()) { + showList() + } else if (loader.query.isEmpty()) { + showSearchHint() + } else { + showNotFound() } } - private fun showDialogFragment(df: DialogFragment, tag: String) { - executeAfterFragmentResumed { activity -> - df.show(activity.supportFragmentManager, tag) + private fun searchUser(accountKey: UserKey, query: String, fromCache: Boolean) { + if (isEmpty(query)) { + showSearchHint() + return + } + val args = Bundle { + this[EXTRA_ACCOUNT_KEY] = accountKey + this[EXTRA_QUERY] = query + this[EXTRA_FROM_CACHE] = fromCache + } + if (loaderInitialized) { + supportLoaderManager.initLoader(0, args, this) + loaderInitialized = true + } else { + supportLoaderManager.restartLoader(0, args, this) } } - override fun onStart() { - super.onStart() - bus.register(this) + private fun showProgress() { + progressContainer.visibility = View.VISIBLE + listContainer.visibility = View.GONE } - override fun onStop() { - bus.unregister(this) - super.onStop() + private fun showSearchHint() { + progressContainer.visibility = View.GONE + listContainer.visibility = View.VISIBLE + emptyView.visibility = View.VISIBLE + listView.visibility = View.GONE + emptyIcon.setImageResource(R.drawable.ic_info_search) + emptyText.text = getText(R.string.search_hint_users) } - - private class SearchUsersTask( - private val activity: UserSelectorActivity, - private val accountKey: UserKey, - private val name: String - ) : AsyncTask>>() { - - override fun doInBackground(vararg params: Any): SingleResponse> { - val twitter = MicroBlogAPIFactory.getInstance(activity, accountKey) ?: return SingleResponse.getInstance>() - try { - val paging = Paging() - val lists = twitter.searchUsers(name, paging) - val data = ArrayList() - for (item in lists) { - data.add(ParcelableUserUtils.fromUser(item, accountKey)) - } - return SingleResponse.getInstance>(data) - } catch (e: MicroBlogException) { - Log.w(LOGTAG, e) - return SingleResponse.getInstance>(e) - } - - } - - override fun onPostExecute(result: SingleResponse>) { - activity.dismissDialogFragment(FRAGMENT_TAG_SEARCH_USERS) - if (result.data != null) { - activity.setUsersData(result.data) - } - } - - override fun onPreExecute() { - val df = ProgressDialogFragment() - df.isCancelable = false - activity.showDialogFragment(df, FRAGMENT_TAG_SEARCH_USERS) - } - - companion object { - - private const val FRAGMENT_TAG_SEARCH_USERS = "search_users" - } - + private fun showNotFound() { + progressContainer.visibility = View.GONE + listContainer.visibility = View.VISIBLE + emptyView.visibility = View.VISIBLE + listView.visibility = View.GONE + emptyIcon.setImageResource(R.drawable.ic_info_search) + emptyText.text = getText(R.string.search_hint_users) } - + private fun showList() { + progressContainer.visibility = View.GONE + listContainer.visibility = View.VISIBLE + listView.visibility = View.VISIBLE + emptyView.visibility = View.GONE + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesConversationFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesConversationFragment.kt index 1b69dbe3e..20eb081e5 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesConversationFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesConversationFragment.kt @@ -53,7 +53,6 @@ import com.squareup.otto.Subscribe import kotlinx.android.synthetic.main.fragment_messages_conversation.* import kotlinx.android.synthetic.main.layout_actionbar_message_user_picker.view.* import me.uucky.colorpicker.internal.EffectViewHelper -import org.mariotaku.sqliteqb.library.Columns.Column import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.sqliteqb.library.OrderBy import org.mariotaku.twidere.R @@ -67,12 +66,14 @@ import org.mariotaku.twidere.annotation.CustomTabType import org.mariotaku.twidere.constant.KeyboardShortcutConstants.ACTION_NAVIGATION_BACK import org.mariotaku.twidere.constant.KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION import org.mariotaku.twidere.constant.SharedPreferenceConstants -import org.mariotaku.twidere.loader.UserSearchLoader -import org.mariotaku.twidere.model.* +import org.mariotaku.twidere.loader.CacheUserSearchLoader +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.ParcelableDirectMessage +import org.mariotaku.twidere.model.ParcelableUser +import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.message.TaskStateChangedEvent import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.provider.TwidereDataStore -import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Conversation import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries @@ -99,8 +100,7 @@ class MessagesConversationFragment : BaseFragment(), LoaderCallbacks, O val query = args.getString(EXTRA_QUERY) val fromCache = args.getBoolean(EXTRA_FROM_CACHE) val fromUser = args.getBoolean(EXTRA_FROM_USER, false) - return CacheUserSearchLoader(this@MessagesConversationFragment, accountKey, query, - fromCache, fromUser) + return CacheUserSearchLoader(context, accountKey, query, fromCache, fromUser) } override fun onLoadFinished(loader: Loader>, data: List?) { @@ -706,49 +706,6 @@ class MessagesConversationFragment : BaseFragment(), LoaderCallbacks, O // }.executeTask(); // } - class CacheUserSearchLoader( - fragment: MessagesConversationFragment, - accountKey: UserKey, - query: String, - private val fromCache: Boolean, - fromUser: Boolean - ) : UserSearchLoader(fragment.context, accountKey, query, 0, null, fromUser) { - private val userColorNameManager: UserColorNameManager - - init { - userColorNameManager = fragment.userColorNameManager - } - - override fun loadInBackground(): List { - val query = query - if (TextUtils.isEmpty(query)) return emptyList() - if (fromCache) { - val cachedList = ArrayList() - val queryEscaped = query.replace("_", "^_") - val nicknameKeys = Utils.getMatchedNicknameKeys(query, userColorNameManager) - val selection = Expression.or(Expression.likeRaw(Column(CachedUsers.SCREEN_NAME), "?||'%'", "^"), - Expression.likeRaw(Column(CachedUsers.NAME), "?||'%'", "^"), - Expression.inArgs(Column(CachedUsers.USER_KEY), nicknameKeys.size)) - val selectionArgs = arrayOf(queryEscaped, queryEscaped, *nicknameKeys) - val order = arrayOf(CachedUsers.LAST_SEEN, CachedUsers.SCREEN_NAME, CachedUsers.NAME) - val ascending = booleanArrayOf(false, true, true) - val orderBy = OrderBy(order, ascending) - val c = context.contentResolver.query(CachedUsers.CONTENT_URI, - CachedUsers.BASIC_COLUMNS, selection?.sql, - selectionArgs, orderBy.sql)!! - val i = ParcelableUserCursorIndices(c) - c.moveToFirst() - while (!c.isAfterLast) { - cachedList.add(i.newObject(c)) - c.moveToNext() - } - c.close() - return cachedList - } - return super.loadInBackground() - } - } - class DeleteConversationConfirmDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(activity) @@ -861,6 +818,6 @@ class MessagesConversationFragment : BaseFragment(), LoaderCallbacks, O // Constants private val LOADER_ID_SEARCH_USERS = 1 - private val EXTRA_FROM_CACHE = "from_cache" + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/CacheUserSearchLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/CacheUserSearchLoader.kt new file mode 100644 index 000000000..99b81905f --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/CacheUserSearchLoader.kt @@ -0,0 +1,59 @@ +package org.mariotaku.twidere.loader + +import android.content.Context +import android.text.TextUtils +import org.mariotaku.sqliteqb.library.Columns +import org.mariotaku.sqliteqb.library.Expression +import org.mariotaku.sqliteqb.library.OrderBy +import org.mariotaku.twidere.model.ParcelableUser +import org.mariotaku.twidere.model.ParcelableUserCursorIndices +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.provider.TwidereDataStore +import org.mariotaku.twidere.util.UserColorNameManager +import org.mariotaku.twidere.util.Utils +import org.mariotaku.twidere.util.dagger.GeneralComponentHelper +import java.util.* +import javax.inject.Inject + +class CacheUserSearchLoader( + context: Context, + accountKey: UserKey, + query: String, + private val fromCache: Boolean, + fromUser: Boolean +) : UserSearchLoader(context, accountKey, query, 0, null, fromUser) { + @Inject + internal lateinit var userColorNameManager: UserColorNameManager + + init { + GeneralComponentHelper.build(context).inject(this) + } + + override fun loadInBackground(): List { + if (TextUtils.isEmpty(query)) return emptyList() + if (fromCache) { + val cachedList = ArrayList() + val queryEscaped = query.replace("_", "^_") + val nicknameKeys = Utils.getMatchedNicknameKeys(query, userColorNameManager) + val selection = Expression.or(Expression.likeRaw(Columns.Column(TwidereDataStore.CachedUsers.SCREEN_NAME), "?||'%'", "^"), + Expression.likeRaw(Columns.Column(TwidereDataStore.CachedUsers.NAME), "?||'%'", "^"), + Expression.inArgs(Columns.Column(TwidereDataStore.CachedUsers.USER_KEY), nicknameKeys.size)) + val selectionArgs = arrayOf(queryEscaped, queryEscaped, *nicknameKeys) + val order = arrayOf(TwidereDataStore.CachedUsers.LAST_SEEN, TwidereDataStore.CachedUsers.SCREEN_NAME, TwidereDataStore.CachedUsers.NAME) + val ascending = booleanArrayOf(false, true, true) + val orderBy = OrderBy(order, ascending) + val c = context.contentResolver.query(TwidereDataStore.CachedUsers.CONTENT_URI, + TwidereDataStore.CachedUsers.BASIC_COLUMNS, selection?.sql, + selectionArgs, orderBy.sql)!! + val i = ParcelableUserCursorIndices(c) + c.moveToFirst() + while (!c.isAfterLast) { + cachedList.add(i.newObject(c)) + c.moveToNext() + } + c.close() + return cachedList + } + return super.loadInBackground() + } +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableUsersLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableUsersLoader.kt index 187f455b9..cd9dbe4d9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableUsersLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableUsersLoader.kt @@ -22,7 +22,6 @@ package org.mariotaku.twidere.loader import android.content.Context import android.support.v4.content.AsyncTaskLoader import android.text.TextUtils -import org.mariotaku.twidere.Constants import org.mariotaku.twidere.loader.iface.IExtendedLoader import org.mariotaku.twidere.model.ParcelableUser import org.mariotaku.twidere.util.collection.NoDuplicatesArrayList @@ -32,7 +31,7 @@ abstract class ParcelableUsersLoader( context: Context, data: List?, override var fromUser: Boolean -) : AsyncTaskLoader>(context), IExtendedLoader, Constants { +) : AsyncTaskLoader>(context), IExtendedLoader { protected val data: MutableList = Collections.synchronizedList(NoDuplicatesArrayList()) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/GeneralComponent.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/GeneralComponent.kt index 6e56b9b18..77d2fe831 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/GeneralComponent.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/dagger/GeneralComponent.kt @@ -28,6 +28,7 @@ import org.mariotaku.twidere.adapter.* import org.mariotaku.twidere.app.TwidereApplication import org.mariotaku.twidere.fragment.* import org.mariotaku.twidere.fragment.filter.FilteredUsersFragment +import org.mariotaku.twidere.loader.CacheUserSearchLoader import org.mariotaku.twidere.loader.MicroBlogAPIStatusesLoader import org.mariotaku.twidere.loader.ParcelableStatusLoader import org.mariotaku.twidere.loader.ParcelableUserLoader @@ -150,4 +151,6 @@ interface GeneralComponent { fun inject(provider: UrlFiltersSubscriptionProvider) fun inject(preference: PremiumEntryPreference) + + fun inject(loader: CacheUserSearchLoader) } diff --git a/twidere/src/main/res/layout/activity_user_selector.xml b/twidere/src/main/res/layout/activity_user_selector.xml index d399013bb..e37a0fed6 100644 --- a/twidere/src/main/res/layout/activity_user_selector.xml +++ b/twidere/src/main/res/layout/activity_user_selector.xml @@ -18,65 +18,49 @@ ~ along with this program. If not, see . --> - + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:gravity="center_vertical" + android:orientation="horizontal" + android:padding="@dimen/element_spacing_normal"> - + android:layout_weight="1" + android:ems="10" + android:hint="@string/search_hint_users" + android:maxLines="1" + app:backgroundTint="?colorAccent"/> - - - - - - - - - - - - + - \ No newline at end of file + + diff --git a/twidere/src/main/res/layout/layout_list_with_empty_view.xml b/twidere/src/main/res/layout/layout_list_with_empty_view.xml index 811fb999d..44cbd75df 100644 --- a/twidere/src/main/res/layout/layout_list_with_empty_view.xml +++ b/twidere/src/main/res/layout/layout_list_with_empty_view.xml @@ -39,6 +39,7 @@ android:id="@+id/emptyView" android:layout_width="match_parent" android:layout_height="match_parent" + android:clickable="true" android:gravity="center" android:orientation="vertical" tools:visibility="visible"> @@ -63,7 +64,8 @@ + android:layout_height="match_parent" + android:clickable="true"> Search Search tweets or users + Search users Search Tweets Tweets Users