MemberChooseActivity.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /* _____ _
  2. * |_ _| |_ _ _ ___ ___ _ __ __ _
  3. * | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. * |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. *
  6. * Threema for Android
  7. * Copyright (c) 2013-2022 Threema GmbH
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. package ch.threema.app.activities;
  22. import android.os.Bundle;
  23. import android.util.SparseArray;
  24. import android.view.Menu;
  25. import android.view.MenuItem;
  26. import android.view.View;
  27. import android.view.ViewGroup;
  28. import android.widget.ImageView;
  29. import android.widget.LinearLayout;
  30. import android.widget.TextView;
  31. import com.google.android.material.appbar.AppBarLayout;
  32. import com.google.android.material.snackbar.Snackbar;
  33. import com.google.android.material.tabs.TabLayout;
  34. import java.util.ArrayList;
  35. import java.util.HashSet;
  36. import java.util.List;
  37. import java.util.Set;
  38. import androidx.annotation.MainThread;
  39. import androidx.annotation.NonNull;
  40. import androidx.annotation.StringRes;
  41. import androidx.appcompat.app.ActionBar;
  42. import androidx.appcompat.widget.SearchView;
  43. import androidx.appcompat.widget.Toolbar;
  44. import androidx.core.view.WindowInsetsCompat;
  45. import androidx.core.view.WindowInsetsControllerCompat;
  46. import androidx.fragment.app.Fragment;
  47. import androidx.fragment.app.FragmentManager;
  48. import androidx.fragment.app.FragmentPagerAdapter;
  49. import androidx.viewpager.widget.ViewPager;
  50. import ch.threema.app.R;
  51. import ch.threema.app.adapters.FilterableListAdapter;
  52. import ch.threema.app.fragments.MemberListFragment;
  53. import ch.threema.app.fragments.UserMemberListFragment;
  54. import ch.threema.app.fragments.WorkUserMemberListFragment;
  55. import ch.threema.app.services.ContactService;
  56. import ch.threema.app.ui.ThreemaSearchView;
  57. import ch.threema.app.utils.AnimationUtil;
  58. import ch.threema.app.utils.ConfigUtils;
  59. import ch.threema.app.utils.LogUtil;
  60. import ch.threema.app.utils.NameUtil;
  61. import ch.threema.app.utils.RuntimeUtil;
  62. import ch.threema.app.utils.SnackbarUtil;
  63. import ch.threema.storage.models.ContactModel;
  64. abstract public class MemberChooseActivity extends ThreemaToolbarActivity implements SearchView.OnQueryTextListener, MemberListFragment.SelectionListener {
  65. private final static int FRAGMENT_USERS = 0;
  66. private final static int FRAGMENT_WORK_USERS = 1;
  67. private final static int NUM_FRAGMENTS = 2;
  68. private MemberChoosePagerAdapter memberChoosePagerAdapter;
  69. private MenuItem searchMenuItem;
  70. private ThreemaSearchView searchView;
  71. protected ContactService contactService;
  72. protected ArrayList<String> excludedIdentities = new ArrayList<>();
  73. protected ArrayList<String> preselectedIdentities = new ArrayList<>();
  74. private ViewPager viewPager;
  75. private final ArrayList<Integer> tabs = new ArrayList<>(NUM_FRAGMENTS);
  76. private Snackbar snackbar;
  77. private View rootView;
  78. @Override
  79. public boolean onQueryTextSubmit(String query) {
  80. // Do something
  81. return true;
  82. }
  83. @Override
  84. public boolean onQueryTextChange(String newText) {
  85. int currentItem = viewPager.getCurrentItem();
  86. Fragment fragment = memberChoosePagerAdapter.getRegisteredFragment(currentItem);
  87. if (fragment != null) {
  88. FilterableListAdapter listAdapter = ((MemberListFragment) fragment).getAdapter();
  89. // adapter can be null if it has not been initialized yet (runs in different thread)
  90. if (listAdapter == null) return false;
  91. listAdapter.getFilter().filter(newText);
  92. }
  93. return true;
  94. }
  95. public int getLayoutResource() {
  96. return R.layout.activity_member_choose_tabbed;
  97. }
  98. @Override
  99. protected boolean initActivity(Bundle savedInstanceState) {
  100. if (!super.initActivity(savedInstanceState)) {
  101. return false;
  102. };
  103. // add notice, if desired
  104. ActionBar actionBar = getSupportActionBar();
  105. if (actionBar != null) {
  106. actionBar.setDisplayHomeAsUpEnabled(true);
  107. Toolbar toolbar = getToolbar();
  108. if (toolbar != null) {
  109. actionBar.setTitle(null);
  110. }
  111. if (getNotice() != 0) {
  112. final TextView noticeText = findViewById(R.id.notice_text);
  113. final LinearLayout noticeLayout = findViewById(R.id.notice_layout);
  114. noticeText.setText(getNotice());
  115. noticeLayout.setVisibility(View.VISIBLE);
  116. ImageView closeButton = findViewById(R.id.close_button);
  117. closeButton.setOnClickListener(new View.OnClickListener() {
  118. @Override
  119. public void onClick(View v) {
  120. AnimationUtil.collapse(noticeLayout);
  121. }
  122. });
  123. }
  124. }
  125. this.rootView = findViewById(R.id.coordinator);
  126. try {
  127. this.contactService = serviceManager.getContactService();
  128. } catch (Exception e) {
  129. LogUtil.exception(e, this);
  130. return false;
  131. }
  132. return true;
  133. }
  134. @MainThread
  135. protected void updateToolbarTitle(@StringRes int title, @StringRes int subtitle) {
  136. getToolbar().setTitle(title);
  137. getToolbar().setSubtitle(subtitle);
  138. }
  139. protected void initList() {
  140. final TabLayout tabLayout = findViewById(R.id.sliding_tabs);
  141. tabs.clear();
  142. viewPager = findViewById(R.id.pager);
  143. if (viewPager == null || tabLayout == null) {
  144. finish();
  145. return;
  146. }
  147. tabLayout.clearOnTabSelectedListeners();
  148. tabLayout.removeAllTabs();
  149. viewPager.clearOnPageChangeListeners();
  150. viewPager.setAdapter(null);
  151. viewPager.removeAllViews();
  152. if (ConfigUtils.isWorkBuild()) {
  153. tabLayout.addTab(tabLayout.newTab()
  154. .setIcon(ConfigUtils.getThemedDrawable(this, R.drawable.ic_work_outline))
  155. .setContentDescription(R.string.title_tab_work_users));
  156. tabs.add(FRAGMENT_WORK_USERS);
  157. }
  158. tabLayout.addTab(tabLayout.newTab()
  159. .setIcon(ConfigUtils.getThemedDrawable(this, R.drawable.ic_person_outline))
  160. .setContentDescription(R.string.title_tab_users));
  161. tabs.add(FRAGMENT_USERS);
  162. // keeps inactive tabs from being destroyed causing all kinds of problems with lingering AsyncTasks on the the adapter
  163. viewPager.setVisibility(View.VISIBLE);
  164. viewPager.setOffscreenPageLimit(1);
  165. tabLayout.setVisibility(tabs.size() > 1 ? View.VISIBLE : View.GONE);
  166. tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
  167. memberChoosePagerAdapter = new MemberChoosePagerAdapter(getSupportFragmentManager());
  168. viewPager.setAdapter(memberChoosePagerAdapter);
  169. viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
  170. tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager));
  171. viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
  172. @Override
  173. public void onPageSelected(int position) {
  174. if (searchMenuItem != null) {
  175. searchMenuItem.collapseActionView();
  176. if (searchView != null) {
  177. searchView.setQuery("", false);
  178. }
  179. }
  180. invalidateOptionsMenu();
  181. }
  182. });
  183. }
  184. @Override
  185. public boolean onCreateOptionsMenu(Menu menu) {
  186. super.onCreateOptionsMenu(menu);
  187. // Inflate the menu; this adds items to the action bar if it is present.
  188. getMenuInflater().inflate(R.menu.activity_member_choose, menu);
  189. if (!getAddNextButton()) {
  190. MenuItem checkItem = menu.findItem(R.id.menu_next);
  191. checkItem.setVisible(false);
  192. }
  193. this.searchMenuItem = menu.findItem(R.id.menu_search_messages);
  194. this.searchView = (ThreemaSearchView) this.searchMenuItem.getActionView();
  195. if (this.searchView != null) {
  196. this.searchView.setQueryHint(getString(R.string.hint_filter_list));
  197. this.searchView.setOnQueryTextListener(this);
  198. } else {
  199. this.searchMenuItem.setVisible(false);
  200. }
  201. return true;
  202. }
  203. @Override
  204. public boolean onOptionsItemSelected(MenuItem item) {
  205. switch (item.getItemId()) {
  206. case android.R.id.home:
  207. if (getAddNextButton()) {
  208. finish();
  209. return true;
  210. }
  211. /* fallthrough */
  212. case R.id.menu_next:
  213. RuntimeUtil.runOnUiThread(new Runnable() {
  214. @Override
  215. public void run() {
  216. if (searchView != null) {
  217. new WindowInsetsControllerCompat(getWindow(), searchView).hide(WindowInsetsCompat.Type.ime());
  218. }
  219. menuNext(getSelectedContacts());
  220. }
  221. });
  222. return true;
  223. }
  224. return super.onOptionsItemSelected(item);
  225. }
  226. protected List<ContactModel> getSelectedContacts() {
  227. Set<ContactModel> contacts = new HashSet<>();
  228. MemberListFragment fragment;
  229. for (int i = 0; i < NUM_FRAGMENTS; i++) {
  230. fragment = (MemberListFragment) memberChoosePagerAdapter.getRegisteredFragment(i);
  231. if (fragment != null) {
  232. contacts.addAll(fragment.getSelectedContacts());
  233. }
  234. }
  235. return new ArrayList<>(contacts);
  236. }
  237. public class MemberChoosePagerAdapter extends FragmentPagerAdapter {
  238. // these globals are not persistent across orientation changes (at least in Android <= 4.1)!
  239. SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
  240. public MemberChoosePagerAdapter(FragmentManager fm) {
  241. super(fm);
  242. }
  243. @Override
  244. public Fragment getItem(int position) {
  245. Fragment fragment = null;
  246. switch (tabs.get(position)) {
  247. case FRAGMENT_USERS:
  248. fragment = new UserMemberListFragment();
  249. break;
  250. case FRAGMENT_WORK_USERS:
  251. fragment = new WorkUserMemberListFragment();
  252. break;
  253. }
  254. if (fragment != null) {
  255. Bundle args = new Bundle();
  256. args.putStringArrayList(MemberListFragment.BUNDLE_ARG_EXCLUDED, excludedIdentities);
  257. args.putStringArrayList(MemberListFragment.BUNDLE_ARG_PRESELECTED, preselectedIdentities);
  258. fragment.setArguments(args);
  259. }
  260. return fragment;
  261. }
  262. @Override
  263. public int getCount() {
  264. return tabs.size();
  265. }
  266. @Override
  267. public CharSequence getPageTitle(int position) {
  268. switch (tabs.get(position)) {
  269. case FRAGMENT_USERS:
  270. return getString(R.string.title_tab_users).toUpperCase();
  271. case FRAGMENT_WORK_USERS:
  272. return getString(R.string.title_tab_work_users).toUpperCase();
  273. }
  274. return null;
  275. }
  276. @NonNull
  277. @Override
  278. public Object instantiateItem(ViewGroup container, int position) {
  279. Fragment fragment = (Fragment) super.instantiateItem(container, position);
  280. registeredFragments.put(position, fragment);
  281. return fragment;
  282. }
  283. @Override
  284. public void destroyItem(ViewGroup container, int position, Object object) {
  285. registeredFragments.remove(position);
  286. super.destroyItem(container, position, object);
  287. }
  288. public Fragment getRegisteredFragment(int position) {
  289. return registeredFragments.get(position);
  290. }
  291. }
  292. @Override
  293. public void onSelectionChanged() {
  294. List<ContactModel> contacts = getSelectedContacts();
  295. if (contacts.size() > 0) {
  296. if (snackbar == null) {
  297. snackbar = SnackbarUtil.make(rootView, "", Snackbar.LENGTH_INDEFINITE, 4);
  298. snackbar.setBackgroundTint(ConfigUtils.getColorFromAttribute(this, R.attr.colorAccent));
  299. snackbar.getView().getLayoutParams().width = AppBarLayout.LayoutParams.MATCH_PARENT;
  300. }
  301. snackbar.setTextColor(ConfigUtils.getColorFromAttribute(this, R.attr.colorOnSecondary));
  302. snackbar.setText(getMemberNames());
  303. if (!snackbar.isShown()) {
  304. snackbar.show();
  305. }
  306. } else {
  307. if (snackbar != null && snackbar.isShown()) {
  308. snackbar.dismiss();
  309. }
  310. }
  311. }
  312. private String getMemberNames() {
  313. StringBuilder builder = new StringBuilder();
  314. for(ContactModel contactModel: getSelectedContacts()) {
  315. if(builder.length() > 0) {
  316. builder.append(", ");
  317. }
  318. builder.append(NameUtil.getDisplayNameOrNickname(contactModel, true));
  319. }
  320. return builder.toString();
  321. }
  322. protected abstract boolean getAddNextButton();
  323. @MainThread
  324. protected abstract void initData(Bundle savedInstanceState);
  325. protected abstract @StringRes int getNotice();
  326. protected abstract void menuNext(List<ContactModel> selectedContacts);
  327. }