Show selected folders in navigation menu

pull/156/head
M66B 6 years ago
parent 3f391a2ddd
commit 6fdc1cae50

File diff suppressed because it is too large Load Diff

@ -37,8 +37,6 @@ import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
@ -52,6 +50,8 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@ -88,8 +88,7 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
private ConstraintLayout drawerContainer; private ConstraintLayout drawerContainer;
private ListView drawerMenu; private RecyclerView rvMenu;
private DrawerAdapter drawerArray;
private boolean hasAccount; private boolean hasAccount;
private String password; private String password;
@ -134,82 +133,98 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
drawerLayout.addDrawerListener(drawerToggle); drawerLayout.addDrawerListener(drawerToggle);
drawerContainer = findViewById(R.id.drawer_container); drawerContainer = findViewById(R.id.drawer_container);
drawerMenu = drawerContainer.findViewById(R.id.drawer_menu); rvMenu = drawerContainer.findViewById(R.id.rvMenu);
rvMenu.setLayoutManager(new LinearLayoutManager(this));
final AdapterNavMenu adapter = new AdapterNavMenu(this, this);
rvMenu.setAdapter(adapter);
drawerArray = new DrawerAdapter(this, false); PackageManager pm = getPackageManager();
drawerMenu.setAdapter(drawerArray); final List<NavMenuItem> menus = new ArrayList<>();
drawerMenu.setOnItemClickListener(new AdapterView.OnItemClickListener() { menus.add(new NavMenuItem(R.drawable.baseline_archive_24, R.string.title_setup_export, new Runnable() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void run() {
DrawerItem item = drawerArray.getItem(position); drawerLayout.closeDrawer(drawerContainer);
if (item == null)
return;
switch (item.getMenuId()) {
case R.string.title_setup_export:
onMenuExport(); onMenuExport();
break; }
case R.string.title_setup_import: }));
menus.add(new NavMenuItem(R.drawable.baseline_unarchive_24, R.string.title_setup_import, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuImport(); onMenuImport();
break;
case R.string.title_setup_theme:
onMenuTheme();
break;
case R.string.title_setup_notifications:
onManageNotifications();
break;
case R.string.title_setup_advanced:
onMenuOptions();
break;
case R.string.menu_contacts:
onMenuContacts();
break;
case R.string.menu_legend:
onMenuLegend();
break;
case R.string.menu_faq:
onMenuFAQ();
break;
case R.string.menu_privacy:
onMenuPrivacy();
break;
case R.string.menu_about:
onMenuAbout();
break;
} }
}));
menus.add(new NavMenuItem(R.drawable.baseline_palette_24, R.string.title_setup_theme, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer); drawerLayout.closeDrawer(drawerContainer);
onMenuTheme();
} }
}); }));
List<DrawerItem> items = new ArrayList<>(); if (getIntentNotifications(this).resolveActivity(pm) != null)
menus.add(new NavMenuItem(R.drawable.baseline_notifications_24, R.string.title_setup_notifications, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onManageNotifications();
}
}));
PackageManager pm = getPackageManager(); menus.add(new NavMenuItem(R.drawable.baseline_settings_applications_24, R.string.title_setup_advanced, new Runnable() {
if (getIntentExport().resolveActivity(pm) != null) @Override
items.add(new DrawerItem(-1, R.drawable.baseline_archive_24, R.string.title_setup_export)); public void run() {
if (getIntentImport().resolveActivity(pm) != null) drawerLayout.closeDrawer(drawerContainer);
items.add(new DrawerItem(-2, R.drawable.baseline_unarchive_24, R.string.title_setup_import)); onMenuOptions();
}
}));
items.add(new DrawerItem(-3)); menus.add(new NavMenuItem(R.drawable.baseline_person_24, R.string.menu_contacts, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuContacts();
}
}));
if (getIntentNotifications(this).resolveActivity(pm) != null) menus.add(new NavMenuItem(R.drawable.baseline_help_24, R.string.menu_legend, new Runnable() {
items.add(new DrawerItem(-4, R.drawable.baseline_notifications_24, R.string.title_setup_notifications)); @Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuLegend();
}
}));
items.add(new DrawerItem(-8, R.drawable.baseline_palette_24, R.string.title_setup_theme)); if (Helper.getIntentFAQ().resolveActivity(pm) != null)
items.add(new DrawerItem(-9, R.drawable.baseline_settings_applications_24, R.string.title_setup_advanced)); menus.add(new NavMenuItem(R.drawable.baseline_question_answer_24, R.string.menu_faq, new Runnable() {
items.add(new DrawerItem(-10, R.drawable.baseline_person_24, R.string.menu_contacts)); @Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuFAQ();
}
}));
items.add(new DrawerItem(-11)); if (Helper.getIntentPrivacy().resolveActivity(pm) != null)
menus.add(new NavMenuItem(R.drawable.baseline_account_box_24, R.string.menu_privacy, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuPrivacy();
}
}));
items.add(new DrawerItem(-12, R.drawable.baseline_help_24, R.string.menu_legend)); menus.add(new NavMenuItem(R.drawable.baseline_info_24, R.string.menu_about, new Runnable() {
if (Helper.getIntentFAQ().resolveActivity(getPackageManager()) != null) @Override
items.add(new DrawerItem(-13, R.drawable.baseline_question_answer_24, R.string.menu_faq)); public void run() {
if (Helper.getIntentPrivacy().resolveActivity(getPackageManager()) != null) drawerLayout.closeDrawer(drawerContainer);
items.add(new DrawerItem(-14, R.drawable.baseline_account_box_24, R.string.menu_privacy)); onMenuAbout();
items.add(new DrawerItem(-15, R.drawable.baseline_info_24, R.string.menu_about)); }
}));
drawerArray.set(items); adapter.set(menus);
getSupportFragmentManager().addOnBackStackChangedListener(this); getSupportFragmentManager().addOnBackStackChangedListener(this);

@ -44,13 +44,12 @@ import android.view.View;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.AdapterView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ScrollView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Group; import androidx.constraintlayout.widget.Group;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
@ -61,6 +60,8 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@ -84,11 +85,9 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import javax.mail.Session; import javax.mail.Session;
@ -102,9 +101,13 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
private Group grpPane; private Group grpPane;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
private ConstraintLayout drawerContainer; private ScrollView drawerContainer;
private ListView drawerMenu; private RecyclerView rvAccount;
private DrawerAdapter drawerArray; private RecyclerView rvFolder;
private RecyclerView rvMenu;
private ImageView ivExpander;
private RecyclerView rvMenuExtra1;
private RecyclerView rvMenuExtra2;
private long message = -1; private long message = -1;
private long attachment = -1; private long attachment = -1;
@ -112,8 +115,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
private WebView printWebView = null; private WebView printWebView = null;
private OpenPgpServiceConnection pgpService; private OpenPgpServiceConnection pgpService;
private NumberFormat nf = NumberFormat.getNumberInstance();
static final int REQUEST_UNIFIED = 1; static final int REQUEST_UNIFIED = 1;
static final int REQUEST_WHY = 2; static final int REQUEST_WHY = 2;
static final int REQUEST_THREAD = 3; static final int REQUEST_THREAD = 3;
@ -151,7 +152,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
startup = prefs.getString("startup", "unified"); startup = prefs.getString("startup", "unified");
view = LayoutInflater.from(this).inflate(R.layout.activity_view, null); view = LayoutInflater.from(this).inflate(R.layout.activity_view, null);
@ -178,216 +179,216 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
drawerLayout.addDrawerListener(drawerToggle); drawerLayout.addDrawerListener(drawerToggle);
drawerContainer = findViewById(R.id.drawer_container); drawerContainer = findViewById(R.id.drawer_container);
drawerMenu = drawerContainer.findViewById(R.id.drawer_menu);
boolean minimal = prefs.getBoolean("minimal", false); rvAccount = drawerContainer.findViewById(R.id.rvAccount);
drawerArray = new DrawerAdapter(this, minimal); rvAccount.setLayoutManager(new LinearLayoutManager(this));
final AdapterNavAccount aadapter = new AdapterNavAccount(this, this);
rvAccount.setAdapter(aadapter);
drawerMenu.setAdapter(drawerArray); rvFolder = drawerContainer.findViewById(R.id.rvFolder);
rvFolder.setLayoutManager(new LinearLayoutManager(this));
final AdapterNavFolder fadapter = new AdapterNavFolder(this, this);
rvFolder.setAdapter(fadapter);
drawerMenu.setOnItemClickListener(new AdapterView.OnItemClickListener() { rvMenu = drawerContainer.findViewById(R.id.rvMenu);
@Override rvMenu.setLayoutManager(new LinearLayoutManager(this));
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final AdapterNavMenu madapter = new AdapterNavMenu(this, this);
DrawerItem item = drawerArray.getItem(position); rvMenu.setAdapter(madapter);
if (item == null)
return;
Log.i("Navigation id=" + item.getId() + " menu=" + item.getMenuId());
switch (item.getMenuId()) { ivExpander = drawerContainer.findViewById(R.id.ivExpander);
case R.string.menu_operations:
onMenuOperations();
break;
case R.string.menu_answers:
onMenuAnswers();
break;
case R.string.menu_setup:
onMenuSetup();
break;
case R.string.title_legend_expander:
onMenuCollapse();
return;
case R.string.menu_legend:
onMenuLegend();
break;
case R.string.menu_faq:
onMenuFAQ();
break;
case R.string.menu_privacy:
onMenuPrivacy();
break;
case R.string.menu_about:
onMenuAbout();
break;
case R.string.menu_pro:
onMenuPro();
break;
case R.string.menu_invite:
onMenuInvite();
break;
case R.string.menu_rate:
onMenuRate();
break;
case R.string.menu_other:
onMenuOtherApps();
break;
default:
long account = item.getId();
if (account > 0)
onMenuFolders(account);
else
onMenuOutbox();
}
drawerLayout.closeDrawer(drawerContainer); rvMenuExtra1 = drawerContainer.findViewById(R.id.rvMenuExtra1);
} rvMenuExtra1.setLayoutManager(new LinearLayoutManager(this));
}); final AdapterNavMenu e1adapter = new AdapterNavMenu(this, this);
rvMenuExtra1.setAdapter(e1adapter);
drawerMenu.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { rvMenuExtra2 = drawerContainer.findViewById(R.id.rvMenuExtra2);
@Override rvMenuExtra2.setLayoutManager(new LinearLayoutManager(this));
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { final AdapterNavMenu e2adapter = new AdapterNavMenu(this, this);
DrawerItem item = drawerArray.getItem(position); rvMenuExtra2.setAdapter(e2adapter);
if (item == null)
return false;
switch (item.getMenuId()) { boolean minimal = prefs.getBoolean("minimal", false);
case R.string.menu_operations: rvMenuExtra1.setVisibility(minimal ? View.GONE : View.VISIBLE);
onShowLog(); rvMenuExtra2.setVisibility(minimal ? View.GONE : View.VISIBLE);
break; ivExpander.setImageLevel(minimal ? 1 /* more */ : 0 /* less */);
case R.string.menu_setup:
onReset();
break;
case R.string.menu_faq:
onDebugInfo();
break;
case R.string.menu_privacy:
onCleanup();
break;
case R.string.menu_about:
if (Helper.isPlayStoreInstall(ActivityView.this))
return false;
checkUpdate(true);
break;
default:
long account = item.getId();
if (account < 0)
return false;
else
onMenuInbox(account);
}
drawerLayout.closeDrawer(drawerContainer); ivExpander.setOnClickListener(new View.OnClickListener() {
return true; @Override
public void onClick(View v) {
boolean minimal = !prefs.getBoolean("minimal", false);
prefs.edit().putBoolean("minimal", minimal).apply();
rvMenuExtra1.setVisibility(minimal ? View.GONE : View.VISIBLE);
rvMenuExtra2.setVisibility(minimal ? View.GONE : View.VISIBLE);
ivExpander.setImageLevel(minimal ? 1 /* more */ : 0 /* less */);
} }
}); });
getSupportFragmentManager().addOnBackStackChangedListener(this); getSupportFragmentManager().addOnBackStackChangedListener(this);
DB db = DB.getInstance(this); final List<NavMenuItem> menus = new ArrayList<>();
db.account().liveAccountsEx(false).observe(this, new Observer<List<TupleAccountEx>>() { final NavMenuItem navOperations = new NavMenuItem(R.drawable.baseline_list_24, R.string.menu_operations, new Runnable() {
private List<TupleAccountEx> last = new ArrayList<>(); @Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuOperations();
}
}, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onShowLog();
}
});
menus.add(navOperations);
menus.add(new NavMenuItem(R.drawable.baseline_reply_24, R.string.menu_answers, new Runnable() {
@Override @Override
public void onChanged(@Nullable List<TupleAccountEx> accounts) { public void run() {
if (accounts == null) drawerLayout.closeDrawer(drawerContainer);
accounts = new ArrayList<>(); onMenuAnswers();
}
}));
boolean changed = false; menus.add(new NavMenuItem(R.drawable.baseline_settings_applications_24, R.string.menu_setup, new Runnable() {
if (last.size() == accounts.size()) { @Override
for (int i = 0; i < accounts.size(); i++) { public void run() {
TupleAccountEx other = last.get(i); drawerLayout.closeDrawer(drawerContainer);
TupleAccountEx account = accounts.get(i); onMenuSetup();
if (!account.id.equals(other.id) ||
!Objects.equals(account.name, other.name) ||
!Objects.equals(account.color, other.color) ||
!Objects.equals(account.state, other.state) ||
account.unseen != other.unseen ||
account.unsent != other.unsent ||
account.operations != other.operations) {
changed = true;
break;
} }
}, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onReset();
} }
} else }));
changed = true;
if (!changed) madapter.set(menus);
return;
last = accounts;
List<DrawerItem> items = new ArrayList<>(); List<NavMenuItem> extra1 = new ArrayList<>();
int unsent = 0; extra1.add(new NavMenuItem(R.drawable.baseline_help_24, R.string.menu_legend, new Runnable() {
int pending = 0; @Override
for (TupleAccountEx account : accounts) { public void run() {
String title; drawerLayout.closeDrawer(drawerContainer);
if (account.unseen > 0) onMenuLegend();
title = getString(R.string.title_name_count, account.name, nf.format(account.unseen));
else
title = account.name;
items.add(new DrawerItem(account.id,
"connected".equals(account.state)
? account.primary ? R.drawable.baseline_folder_special_24 : R.drawable.baseline_folder_24
: R.drawable.baseline_folder_open_24,
title, account.color, account.unseen > 0));
unsent += account.unsent;
pending += account.operations;
} }
}));
items.add(new DrawerItem(-1)); if (Helper.getIntentFAQ().resolveActivity(getPackageManager()) != null)
extra1.add(new NavMenuItem(R.drawable.baseline_question_answer_24, R.string.menu_faq, new Runnable() {
String outbox; @Override
if (unsent > 0) public void run() {
outbox = getString(R.string.title_name_count, getString(R.string.title_folder_outbox), nf.format(unsent)); drawerLayout.closeDrawer(drawerContainer);
else onMenuFAQ();
outbox = getString(R.string.title_folder_outbox); }
items.add(new DrawerItem(-2, R.drawable.baseline_send_24, outbox, null, unsent > 0)); }, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onDebugInfo();
}
}));
String operations; if (Helper.getIntentPrivacy().resolveActivity(getPackageManager()) != null)
if (pending == 0) extra1.add(new NavMenuItem(R.drawable.baseline_account_box_24, R.string.menu_privacy, new Runnable() {
operations = getString(R.string.menu_operations); @Override
else public void run() {
operations = getString(R.string.title_name_count, drawerLayout.closeDrawer(drawerContainer);
getString(R.string.menu_operations), onMenuPrivacy();
nf.format(pending)); }
items.add(new DrawerItem(-3, R.string.menu_operations, R.drawable.baseline_list_24, operations, pending > 0)); }, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onCleanup();
}
}));
items.add(new DrawerItem(-4, R.drawable.baseline_reply_24, R.string.menu_answers)); extra1.add(new NavMenuItem(R.drawable.baseline_info_24, R.string.menu_about, new Runnable() {
@Override
public void run() {
onMenuAbout();
}
}, new Runnable() {
@Override
public void run() {
if (Helper.isPlayStoreInstall(ActivityView.this)) {
drawerLayout.closeDrawer(drawerContainer);
checkUpdate(true);
}
}
}));
items.add(new DrawerItem(-5, R.drawable.baseline_settings_applications_24, R.string.menu_setup)); e1adapter.set(extra1);
items.add(new DrawerItem(-6));
items.add(new DrawerItem(-7, R.string.title_legend_expander));
items.add(new DrawerItem(-8, R.drawable.baseline_help_24, R.string.menu_legend).setCollapsible());
if (Helper.getIntentFAQ().resolveActivity(getPackageManager()) != null) List<NavMenuItem> extra2 = new ArrayList<>();
items.add(new DrawerItem(-9, R.drawable.baseline_question_answer_24, R.string.menu_faq).setCollapsible());
if (Helper.getIntentPrivacy().resolveActivity(getPackageManager()) != null) if (getIntentPro() == null || getIntentPro().resolveActivity(getPackageManager()) != null)
items.add(new DrawerItem(-10, R.drawable.baseline_account_box_24, R.string.menu_privacy).setCollapsible()); extra2.add(new NavMenuItem(R.drawable.baseline_monetization_on_24, R.string.menu_pro, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuPro();
}
}));
items.add(new DrawerItem(-11, R.drawable.baseline_info_24, R.string.menu_about).setCollapsible()); if ((getIntentInvite().resolveActivity(getPackageManager()) != null))
extra2.add(new NavMenuItem(R.drawable.baseline_people_24, R.string.menu_invite, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuInvite();
}
}));
boolean pro = (getIntentPro() == null || getIntentPro().resolveActivity(getPackageManager()) != null); if (getIntentRate().resolveActivity(getPackageManager()) != null)
boolean invite = (getIntentInvite().resolveActivity(getPackageManager()) != null); extra2.add(new NavMenuItem(R.drawable.baseline_star_24, R.string.menu_rate, new Runnable() {
boolean rate = (getIntentRate().resolveActivity(getPackageManager()) != null); @Override
boolean other = (getIntentOtherApps().resolveActivity(getPackageManager()) != null); public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuRate();
}
}));
if (pro || invite || rate || other) if (getIntentOtherApps().resolveActivity(getPackageManager()) != null)
items.add(new DrawerItem(-12).setCollapsible()); extra2.add(new NavMenuItem(R.drawable.baseline_get_app_24, R.string.menu_other, new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(drawerContainer);
onMenuOtherApps();
}
}));
if (pro) e2adapter.set(extra2);
items.add(new DrawerItem(-13, R.drawable.baseline_monetization_on_24, R.string.menu_pro).setCollapsible());
if (invite) DB db = DB.getInstance(this);
items.add(new DrawerItem(-14, R.drawable.baseline_people_24, R.string.menu_invite).setCollapsible());
if (rate) db.account().liveAccountsEx(false).observe(this, new Observer<List<TupleAccountEx>>() {
items.add(new DrawerItem(-15, R.drawable.baseline_star_24, R.string.menu_rate).setCollapsible()); @Override
public void onChanged(@Nullable List<TupleAccountEx> accounts) {
if (accounts == null)
accounts = new ArrayList<>();
aadapter.set(accounts);
}
});
if (other) db.folder().liveNavigation().observe(this, new Observer<List<TupleFolderNav>>() {
items.add(new DrawerItem(-16, R.drawable.baseline_get_app_24, R.string.menu_other).setCollapsible()); @Override
public void onChanged(List<TupleFolderNav> folders) {
if (folders == null)
folders = new ArrayList<>();
fadapter.set(folders);
}
});
drawerArray.set(items); db.operation().liveCount().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer count) {
navOperations.setCount(count);
madapter.notifyDataSetChanged();
} }
}); });
@ -961,8 +962,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean minimal = !prefs.getBoolean("minimal", false); boolean minimal = !prefs.getBoolean("minimal", false);
prefs.edit().putBoolean("minimal", minimal).apply(); prefs.edit().putBoolean("minimal", minimal).apply();
drawerArray.set(minimal); //drawerArray.set(minimal);
drawerArray.notifyDataSetChanged(); //drawerArray.notifyDataSetChanged();
} }
private void onMenuLegend() { private void onMenuLegend() {

@ -919,7 +919,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
bnvActions.getMenu().findItem(R.id.action_more).setVisible(!inOutbox); bnvActions.getMenu().findItem(R.id.action_more).setVisible(!inOutbox);
bnvActions.getMenu().findItem(R.id.action_delete).setVisible( bnvActions.getMenu().findItem(R.id.action_delete).setVisible(debug ||
(inTrash && message.msgid != null) || (inTrash && message.msgid != null) ||
(!inTrash && hasTrash && message.uid != null) || (!inTrash && hasTrash && message.uid != null) ||
(inOutbox && (!TextUtils.isEmpty(message.error) || !message.identitySynchronize))); (inOutbox && (!TextUtils.isEmpty(message.error) || !message.identitySynchronize)));

@ -0,0 +1,206 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class AdapterNavAccount extends RecyclerView.Adapter<AdapterNavAccount.ViewHolder> {
private Context context;
private LifecycleOwner owner;
private LayoutInflater inflater;
private List<TupleAccountEx> items = new ArrayList<>();
private NumberFormat nf = NumberFormat.getNumberInstance();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private View view;
private ImageView ivItem;
private TextView tvItem;
ViewHolder(View itemView) {
super(itemView);
view = itemView.findViewById(R.id.clItem);
ivItem = itemView.findViewById(R.id.ivItem);
tvItem = itemView.findViewById(R.id.tvItem);
}
private void wire() {
view.setOnClickListener(this);
}
private void unwire() {
view.setOnClickListener(null);
}
private void bindTo(TupleAccountEx account) {
ivItem.setImageResource("connected".equals(account.state)
? account.primary ? R.drawable.baseline_folder_special_24 : R.drawable.baseline_folder_24
: R.drawable.baseline_folder_open_24);
if (account.color == null)
ivItem.clearColorFilter();
else
ivItem.setColorFilter(account.color);
if (account.unseen == 0)
tvItem.setText(account.name);
else
tvItem.setText(context.getString(R.string.title_name_count,
account.name, nf.format(account.unseen)));
tvItem.setTextColor(Helper.resolveColor(context,
account.unseen == 0 ? android.R.attr.textColorSecondary : R.attr.colorUnread));
}
@Override
public void onClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return;
TupleAccountEx account = items.get(pos);
if (account == null)
return;
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_FOLDERS)
.putExtra("id", account.id));
}
}
AdapterNavAccount(Context context, LifecycleOwner owner) {
this.context = context;
this.owner = owner;
this.inflater = LayoutInflater.from(context);
setHasStableIds(true);
}
public void set(@NonNull List<TupleAccountEx> accounts) {
Log.i("Set nav accounts=" + accounts.size());
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, accounts), false);
items = accounts;
diff.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
Log.i("Inserted @" + position + " #" + count);
}
@Override
public void onRemoved(int position, int count) {
Log.i("Removed @" + position + " #" + count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
Log.i("Moved " + fromPosition + ">" + toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
Log.i("Changed @" + position + " #" + count);
}
});
diff.dispatchUpdatesTo(this);
}
private class DiffCallback extends DiffUtil.Callback {
private List<TupleAccountEx> prev = new ArrayList<>();
private List<TupleAccountEx> next = new ArrayList<>();
DiffCallback(List<TupleAccountEx> prev, List<TupleAccountEx> next) {
this.prev.addAll(prev);
this.next.addAll(next);
}
@Override
public int getOldListSize() {
return prev.size();
}
@Override
public int getNewListSize() {
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
TupleAccountEx a1 = prev.get(oldItemPosition);
TupleAccountEx a2 = next.get(newItemPosition);
return a1.id.equals(a2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
TupleAccountEx a1 = prev.get(oldItemPosition);
TupleAccountEx a2 = next.get(newItemPosition);
return Objects.equals(a1.name, a2.name) &&
Objects.equals(a1.color, a2.color) &&
a1.unseen == a2.unseen &&
Objects.equals(a1.state, a2.state);
}
}
@Override
public long getItemId(int position) {
return items.get(position).id;
}
@Override
public int getItemCount() {
return items.size();
}
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.item_nav, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
TupleAccountEx account = items.get(position);
holder.bindTo(account);
holder.wire();
}
}

@ -0,0 +1,225 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import java.text.Collator;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
public class AdapterNavFolder extends RecyclerView.Adapter<AdapterNavFolder.ViewHolder> {
private Context context;
private LifecycleOwner owner;
private LayoutInflater inflater;
private List<TupleFolderNav> items = new ArrayList<>();
private NumberFormat nf = NumberFormat.getNumberInstance();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private View view;
private ImageView ivItem;
private TextView tvItem;
ViewHolder(View itemView) {
super(itemView);
view = itemView.findViewById(R.id.clItem);
ivItem = itemView.findViewById(R.id.ivItem);
tvItem = itemView.findViewById(R.id.tvItem);
}
private void wire() {
view.setOnClickListener(this);
}
private void unwire() {
view.setOnClickListener(null);
}
private void bindTo(TupleFolderNav folder) {
ivItem.setImageResource("connected".equals(folder.state)
? R.drawable.baseline_folder_24
: R.drawable.baseline_folder_open_24);
if (folder.color == null)
ivItem.clearColorFilter();
else
ivItem.setColorFilter(folder.color);
int count = (EntityFolder.OUTBOX.equals(folder.type) ? folder.operations : folder.unseen);
if (count == 0)
tvItem.setText(folder.getDisplayName(context));
else
tvItem.setText(context.getString(R.string.title_name_count,
folder.getDisplayName(context), nf.format(count)));
tvItem.setTextColor(Helper.resolveColor(context,
count == 0 ? android.R.attr.textColorSecondary : R.attr.colorUnread));
}
@Override
public void onClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return;
TupleFolderNav folder = items.get(pos);
if (folder == null)
return;
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_MESSAGES)
.putExtra("account", folder.account)
.putExtra("folder", folder.id));
}
}
AdapterNavFolder(Context context, LifecycleOwner owner) {
this.context = context;
this.owner = owner;
this.inflater = LayoutInflater.from(context);
setHasStableIds(true);
}
public void set(@NonNull List<TupleFolderNav> folders) {
Log.i("Set nav folders=" + folders.size());
final Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(folders, new Comparator<EntityFolder>() {
@Override
public int compare(EntityFolder f1, EntityFolder f2) {
int o = Boolean.compare(EntityFolder.OUTBOX.equals(f1.type), EntityFolder.OUTBOX.equals(f2.type));
if (o != 0)
return o;
String name1 = f1.getDisplayName(context);
String name2 = f2.getDisplayName(context);
return collator.compare(name1, name2);
}
});
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, folders), false);
items = folders;
diff.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
Log.i("Inserted @" + position + " #" + count);
}
@Override
public void onRemoved(int position, int count) {
Log.i("Removed @" + position + " #" + count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
Log.i("Moved " + fromPosition + ">" + toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
Log.i("Changed @" + position + " #" + count);
}
});
diff.dispatchUpdatesTo(this);
}
private class DiffCallback extends DiffUtil.Callback {
private List<TupleFolderNav> prev = new ArrayList<>();
private List<TupleFolderNav> next = new ArrayList<>();
DiffCallback(List<TupleFolderNav> prev, List<TupleFolderNav> next) {
this.prev.addAll(prev);
this.next.addAll(next);
}
@Override
public int getOldListSize() {
return prev.size();
}
@Override
public int getNewListSize() {
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
TupleFolderNav f1 = prev.get(oldItemPosition);
TupleFolderNav f2 = next.get(newItemPosition);
return f1.id.equals(f2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
TupleFolderNav f1 = prev.get(oldItemPosition);
TupleFolderNav f2 = next.get(newItemPosition);
return f1.equals(f2);
}
}
@Override
public long getItemId(int position) {
return items.get(position).id;
}
@Override
public int getItemCount() {
return items.size();
}
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.item_nav, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
TupleFolderNav folder = items.get(position);
holder.bindTo(folder);
holder.wire();
}
}

@ -0,0 +1,200 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
public class AdapterNavMenu extends RecyclerView.Adapter<AdapterNavMenu.ViewHolder> {
private Context context;
private LifecycleOwner owner;
private LayoutInflater inflater;
private List<NavMenuItem> items = new ArrayList<>();
private NumberFormat nf = NumberFormat.getNumberInstance();
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
private View view;
private ImageView ivItem;
private TextView tvItem;
ViewHolder(View itemView) {
super(itemView);
view = itemView.findViewById(R.id.clItem);
ivItem = itemView.findViewById(R.id.ivItem);
tvItem = itemView.findViewById(R.id.tvItem);
}
private void wire() {
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
private void unwire() {
view.setOnClickListener(null);
view.setOnLongClickListener(null);
}
private void bindTo(NavMenuItem menu) {
ivItem.setImageResource(menu.getIcon());
if (menu.getCount() == null)
tvItem.setText(menu.getTitle());
else
tvItem.setText(context.getString(R.string.title_name_count,
context.getString(menu.getTitle()), nf.format(menu.getCount())));
tvItem.setTextColor(Helper.resolveColor(context,
menu.getCount() == null ? android.R.attr.textColorSecondary : R.attr.colorUnread));
}
@Override
public void onClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return;
NavMenuItem menu = items.get(pos);
menu.onClick();
}
@Override
public boolean onLongClick(View v) {
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return false;
NavMenuItem menu = items.get(pos);
return menu.onLongClick();
}
}
AdapterNavMenu(Context context, LifecycleOwner owner) {
this.context = context;
this.owner = owner;
this.inflater = LayoutInflater.from(context);
setHasStableIds(true);
}
public void set(@NonNull List<NavMenuItem> menus) {
Log.i("Set nav menus=" + menus.size());
DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(items, menus), false);
items = menus;
diff.dispatchUpdatesTo(new ListUpdateCallback() {
@Override
public void onInserted(int position, int count) {
Log.i("Inserted @" + position + " #" + count);
}
@Override
public void onRemoved(int position, int count) {
Log.i("Removed @" + position + " #" + count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
Log.i("Moved " + fromPosition + ">" + toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
Log.i("Changed @" + position + " #" + count);
}
});
diff.dispatchUpdatesTo(this);
}
private class DiffCallback extends DiffUtil.Callback {
private List<NavMenuItem> prev = new ArrayList<>();
private List<NavMenuItem> next = new ArrayList<>();
DiffCallback(List<NavMenuItem> prev, List<NavMenuItem> next) {
this.prev.addAll(prev);
this.next.addAll(next);
}
@Override
public int getOldListSize() {
return prev.size();
}
@Override
public int getNewListSize() {
return next.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
NavMenuItem m1 = prev.get(oldItemPosition);
NavMenuItem m2 = next.get(newItemPosition);
return m1.getTitle() == m2.getTitle();
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
NavMenuItem m1 = prev.get(oldItemPosition);
NavMenuItem m2 = next.get(newItemPosition);
return m1.equals(m2);
}
}
@Override
public long getItemId(int position) {
return items.get(position).getTitle();
}
@Override
public int getItemCount() {
return items.size();
}
@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.item_nav, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
NavMenuItem menu = items.get(position);
holder.bindTo(menu);
holder.wire();
}
}

@ -51,7 +51,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 74, version = 75,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -766,6 +766,13 @@ public abstract class DB extends RoomDatabase {
db.execSQL("ALTER TABLE `folder` ADD COLUMN `subscribed` INTEGER"); db.execSQL("ALTER TABLE `folder` ADD COLUMN `subscribed` INTEGER");
} }
}) })
.addMigrations(new Migration(74, 75) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `folder` ADD COLUMN `navigation` INTEGER NOT NULL DEFAULT 0");
}
})
.build(); .build();
} }

@ -43,7 +43,7 @@ public interface DaoAccount {
@Query("SELECT * FROM account WHERE synchronize") @Query("SELECT * FROM account WHERE synchronize")
LiveData<List<EntityAccount>> liveSynchronizingAccounts(); LiveData<List<EntityAccount>> liveSynchronizingAccounts();
@Query("SELECT account.*, COUNT(operation.id) AS operations" + @Query("SELECT account.*" +
", (SELECT COUNT(message.id)" + ", (SELECT COUNT(message.id)" +
" FROM message" + " FROM message" +
" JOIN folder ON folder.id = message.folder" + " JOIN folder ON folder.id = message.folder" +
@ -58,16 +58,8 @@ public interface DaoAccount {
" FROM identity" + " FROM identity" +
" WHERE identity.account = account.id" + " WHERE identity.account = account.id" +
" AND identity.synchronize) AS identities" + " AND identity.synchronize) AS identities" +
", (SELECT COUNT(message.id)" +
" FROM message" +
" JOIN folder ON folder.id = message.folder" +
" WHERE message.account = account.id" +
" AND folder.type = '" + EntityFolder.OUTBOX + "'" +
" AND NOT ui_seen" +
" AND NOT ui_hide) AS unsent" +
", CASE WHEN drafts.id IS NULL THEN 0 ELSE 1 END AS drafts" + ", CASE WHEN drafts.id IS NULL THEN 0 ELSE 1 END AS drafts" +
" FROM account" + " FROM account" +
" LEFT JOIN operation ON operation.account = account.id" +
" LEFT JOIN folder AS drafts ON drafts.account = account.id AND drafts.type = '" + EntityFolder.DRAFTS + "'" + " LEFT JOIN folder AS drafts ON drafts.account = account.id AND drafts.type = '" + EntityFolder.DRAFTS + "'" +
" WHERE :all OR account.synchronize" + " WHERE :all OR account.synchronize" +
" GROUP BY account.id" + " GROUP BY account.id" +

@ -82,6 +82,18 @@ public interface DaoFolder {
" GROUP BY folder.id") " GROUP BY folder.id")
LiveData<List<TupleFolderEx>> liveUnified(); LiveData<List<TupleFolderEx>> liveUnified();
@Query("SELECT folder.*" +
", account.color" +
", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" +
", (SELECT COUNT(*) FROM operation WHERE operation.folder = folder.id) AS operations" +
" FROM folder" +
" LEFT JOIN account ON account.id = folder.account" +
" LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide" +
" WHERE account.id IS NULL" +
" OR (account.`synchronize` AND folder.navigation)" +
" GROUP BY folder.id")
LiveData<List<TupleFolderNav>> liveNavigation();
@Query("SELECT folder.* FROM folder" + @Query("SELECT folder.* FROM folder" +
" JOIN account ON account.id = folder.account" + " JOIN account ON account.id = folder.account" +
" WHERE account.synchronize" + " WHERE account.synchronize" +
@ -180,6 +192,7 @@ public interface DaoFolder {
@Query("UPDATE folder" + @Query("UPDATE folder" +
" SET display = :display" + " SET display = :display" +
", unified = :unified" + ", unified = :unified" +
", navigation = :navigation" +
", notify = :notify" + ", notify = :notify" +
", hide = :hide" + ", hide = :hide" +
", synchronize = :synchronize" + ", synchronize = :synchronize" +
@ -190,7 +203,7 @@ public interface DaoFolder {
" WHERE id = :id") " WHERE id = :id")
int setFolderProperties( int setFolderProperties(
long id, long id,
String display, boolean unified, boolean notify, boolean hide, String display, boolean unified, boolean navigation, boolean notify, boolean hide,
boolean synchronize, boolean poll, boolean download, boolean synchronize, boolean poll, boolean download,
int sync_days, int keep_days); int sync_days, int keep_days);

@ -65,6 +65,9 @@ public interface DaoOperation {
@Query(GET_OPS_FOLDER) @Query(GET_OPS_FOLDER)
LiveData<List<EntityOperation>> liveOperations(long folder); LiveData<List<EntityOperation>> liveOperations(long folder);
@Query("SELECT COUNT(operation.id) FROM operation")
LiveData<Integer> liveCount();
@Query("SELECT COUNT(operation.id) FROM operation" + @Query("SELECT COUNT(operation.id) FROM operation" +
" WHERE operation.name = '" + EntityOperation.SEND + "'") " WHERE operation.name = '" + EntityOperation.SEND + "'")
LiveData<Integer> liveUnsent(); LiveData<Integer> liveUnsent();

@ -1,91 +0,0 @@
package eu.faircode.email;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class DrawerAdapter extends ArrayAdapter<DrawerItem> {
private boolean collapsed;
private List<DrawerItem> items = new ArrayList<>();
DrawerAdapter(@NonNull Context context, boolean collapsed) {
super(context, -1);
this.collapsed = collapsed;
}
@NonNull
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
DrawerItem item = getItem(position);
View row = item.isCollapsible() && collapsed
? new View(getContext())
: LayoutInflater.from(getContext()).inflate(item.getLayout(), null);
ImageView iv = row.findViewById(R.id.ivItem);
TextView tv = row.findViewById(R.id.tvItem);
ImageView expander = row.findViewById(R.id.ivExpander);
if (iv != null) {
iv.setImageResource(item.getIcon());
if (item.getColor() != null)
iv.setColorFilter(item.getColor());
}
if (tv != null) {
tv.setText(item.getTitle(getContext()));
tv.setTextColor(Helper.resolveColor(getContext(),
item.getHighlight() ? R.attr.colorUnread : android.R.attr.textColorSecondary));
}
if (expander != null)
expander.setImageLevel(collapsed ? 1 /* more */ : 0 /* less */);
return row;
}
void set(boolean collapsed) {
this.collapsed = collapsed;
notifyDataSetChanged();
}
void set(List<DrawerItem> items) {
this.items = items;
notifyDataSetChanged();
}
@Override
public int getCount() {
return items.size();
}
@Nullable
@Override
public DrawerItem getItem(int position) {
if (position < items.size())
return items.get(position);
else
return null;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position) {
DrawerItem item = getItem(position);
return (item == null ? 0 : item.getId());
}
}

@ -1,95 +0,0 @@
package eu.faircode.email;
import android.content.Context;
public class DrawerItem {
private long id;
private int menu;
private int layout;
private int icon;
private Integer color;
private int resid;
private String title;
private boolean highlight;
private boolean collapsible = false;
DrawerItem(long id) {
this.id = id;
this.layout = R.layout.item_drawer_separator;
}
DrawerItem(long id, int resid) {
this.id = id;
this.menu = resid;
this.layout = R.layout.item_drawer_expander;
}
DrawerItem(long id, int icon, int resid) {
this.id = id;
this.menu = resid;
this.layout = R.layout.item_drawer;
this.icon = icon;
this.resid = resid;
}
DrawerItem(long id, int menu, int icon, String title, boolean highlight) {
this.id = id;
this.menu = menu;
this.layout = R.layout.item_drawer;
this.icon = icon;
this.title = title;
this.highlight = highlight;
}
DrawerItem(long id, int icon, String title, Integer color, boolean highlight) {
this.id = id;
this.layout = R.layout.item_drawer;
this.icon = icon;
this.color = color;
this.title = title;
this.highlight = highlight;
}
DrawerItem setCollapsible() {
this.collapsible = true;
return this;
}
int getLayout() {
return this.layout;
}
long getId() {
return this.id;
}
boolean isEnabled() {
return (this.layout != R.layout.item_drawer_separator);
}
int getMenuId() {
return this.menu;
}
int getIcon() {
return this.icon;
}
Integer getColor() {
return this.color;
}
String getTitle(Context context) {
if (this.title == null && resid > 0)
this.title = context.getString(resid);
return this.title;
}
boolean getHighlight() {
return this.highlight;
}
boolean isCollapsible() {
return this.collapsible;
}
}

@ -91,6 +91,8 @@ public class EntityFolder implements Serializable {
@NonNull @NonNull
public Boolean unified = false; public Boolean unified = false;
@NonNull @NonNull
public Boolean navigation = false;
@NonNull
public Boolean notify = false; public Boolean notify = false;
public Integer total; // messages on server public Integer total; // messages on server

@ -50,6 +50,7 @@ public class FragmentFolder extends FragmentBase {
private EditText etDisplay; private EditText etDisplay;
private CheckBox cbHide; private CheckBox cbHide;
private CheckBox cbUnified; private CheckBox cbUnified;
private CheckBox cbNavigation;
private CheckBox cbNotify; private CheckBox cbNotify;
private CheckBox cbSynchronize; private CheckBox cbSynchronize;
private CheckBox cbPoll; private CheckBox cbPoll;
@ -90,6 +91,7 @@ public class FragmentFolder extends FragmentBase {
etDisplay = view.findViewById(R.id.etDisplay); etDisplay = view.findViewById(R.id.etDisplay);
cbHide = view.findViewById(R.id.cbHide); cbHide = view.findViewById(R.id.cbHide);
cbUnified = view.findViewById(R.id.cbUnified); cbUnified = view.findViewById(R.id.cbUnified);
cbNavigation = view.findViewById(R.id.cbNavigation);
cbNotify = view.findViewById(R.id.cbNotify); cbNotify = view.findViewById(R.id.cbNotify);
cbSynchronize = view.findViewById(R.id.cbSynchronize); cbSynchronize = view.findViewById(R.id.cbSynchronize);
cbPoll = view.findViewById(R.id.cbPoll); cbPoll = view.findViewById(R.id.cbPoll);
@ -151,6 +153,7 @@ public class FragmentFolder extends FragmentBase {
args.putString("display", etDisplay.getText().toString()); args.putString("display", etDisplay.getText().toString());
args.putBoolean("hide", cbHide.isChecked()); args.putBoolean("hide", cbHide.isChecked());
args.putBoolean("unified", cbUnified.isChecked()); args.putBoolean("unified", cbUnified.isChecked());
args.putBoolean("navigation", cbNavigation.isChecked());
args.putBoolean("notify", cbNotify.getVisibility() == View.VISIBLE && cbNotify.isChecked()); args.putBoolean("notify", cbNotify.getVisibility() == View.VISIBLE && cbNotify.isChecked());
args.putBoolean("synchronize", cbSynchronize.isChecked()); args.putBoolean("synchronize", cbSynchronize.isChecked());
args.putBoolean("poll", cbPoll.isChecked()); args.putBoolean("poll", cbPoll.isChecked());
@ -185,6 +188,7 @@ public class FragmentFolder extends FragmentBase {
String display = args.getString("display"); String display = args.getString("display");
boolean hide = args.getBoolean("hide"); boolean hide = args.getBoolean("hide");
boolean unified = args.getBoolean("unified"); boolean unified = args.getBoolean("unified");
boolean navigation = args.getBoolean("navigation");
boolean notify = args.getBoolean("notify"); boolean notify = args.getBoolean("notify");
boolean synchronize = args.getBoolean("synchronize"); boolean synchronize = args.getBoolean("synchronize");
boolean poll = args.getBoolean("poll"); boolean poll = args.getBoolean("poll");
@ -220,6 +224,7 @@ public class FragmentFolder extends FragmentBase {
create.hide = hide; create.hide = hide;
create.type = EntityFolder.USER; create.type = EntityFolder.USER;
create.unified = unified; create.unified = unified;
create.navigation = navigation;
create.notify = notify; create.notify = notify;
create.synchronize = synchronize; create.synchronize = synchronize;
create.poll = poll; create.poll = poll;
@ -234,7 +239,7 @@ public class FragmentFolder extends FragmentBase {
Log.i("Updating folder=" + name); Log.i("Updating folder=" + name);
db.folder().setFolderProperties(id, db.folder().setFolderProperties(id,
display, unified, notify, hide, display, unified, navigation, notify, hide,
synchronize, poll, download, synchronize, poll, download,
sync_days, keep_days); sync_days, keep_days);
db.folder().setFolderError(id, null); db.folder().setFolderError(id, null);
@ -414,6 +419,7 @@ public class FragmentFolder extends FragmentBase {
etDisplay.setHint(folder == null ? null : folder.name); etDisplay.setHint(folder == null ? null : folder.name);
cbHide.setChecked(folder == null ? false : folder.hide); cbHide.setChecked(folder == null ? false : folder.hide);
cbUnified.setChecked(folder == null ? false : folder.unified); cbUnified.setChecked(folder == null ? false : folder.unified);
cbNavigation.setChecked(folder == null ? false : folder.navigation);
cbNotify.setChecked(folder == null ? false : folder.notify); cbNotify.setChecked(folder == null ? false : folder.notify);
cbSynchronize.setChecked(folder == null || folder.synchronize); cbSynchronize.setChecked(folder == null || folder.synchronize);
cbPoll.setChecked(folder == null ? false : folder.poll); cbPoll.setChecked(folder == null ? false : folder.poll);

@ -0,0 +1,65 @@
package eu.faircode.email;
import androidx.annotation.Nullable;
import java.util.Objects;
public class NavMenuItem {
private int icon;
private int title;
private Integer count = null;
private Runnable click;
private Runnable longClick;
NavMenuItem(int icon, int title, Runnable click) {
this.icon = icon;
this.title = title;
this.click = click;
}
NavMenuItem(int icon, int title, Runnable click, Runnable longClick) {
this.icon = icon;
this.title = title;
this.click = click;
this.longClick = longClick;
}
void setCount(Integer count) {
if (count != null && count == 0)
count = null;
this.count = count;
}
int getIcon() {
return this.icon;
}
int getTitle() {
return this.title;
}
Integer getCount() {
return this.count;
}
void onClick() {
click.run();
}
boolean onLongClick() {
if (longClick != null)
longClick.run();
return (longClick != null);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof NavMenuItem) {
NavMenuItem other = (NavMenuItem) obj;
return (this.icon == other.icon &&
this.title == other.title &&
Objects.equals(this.count, other.count));
} else
return false;
}
}

@ -21,8 +21,6 @@ package eu.faircode.email;
public class TupleAccountEx extends EntityAccount { public class TupleAccountEx extends EntityAccount {
public int unseen; public int unseen;
public int unsent;
public int operations;
public int identities; // synchronizing public int identities; // synchronizing
public boolean drafts; public boolean drafts;
@ -43,8 +41,6 @@ public class TupleAccountEx extends EntityAccount {
TupleAccountEx other = (TupleAccountEx) obj; TupleAccountEx other = (TupleAccountEx) obj;
return (super.equals(obj) && return (super.equals(obj) &&
this.unseen == other.unseen && this.unseen == other.unseen &&
this.unsent == other.unsent &&
this.operations == other.operations &&
this.identities == other.identities && this.identities == other.identities &&
this.drafts == other.drafts); this.drafts == other.drafts);
} else } else

@ -0,0 +1,41 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail 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.
FairEmail 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 FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import java.io.Serializable;
import java.util.Objects;
public class TupleFolderNav extends EntityFolder implements Serializable {
public Integer color; // account
public int unseen;
public int operations;
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleFolderNav) {
TupleFolderNav other = (TupleFolderNav) obj;
return (super.equals(obj) &&
Objects.equals(this.color, other.color) &&
this.unseen == other.unseen &&
this.operations == other.operations);
} else
return false;
}
}

@ -43,22 +43,17 @@
app:constraint_referenced_ids="content_separator,content_pane" /> app:constraint_referenced_ids="content_separator,content_pane" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout <ScrollView
android:id="@+id/drawer_container" android:id="@+id/drawer_container"
android:layout_width="270dp" android:layout_width="270dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:background="?attr/colorDrawerBackground"> android:background="?attr/colorDrawerBackground"
android:orientation="vertical">
<ListView <include
android:id="@+id/drawer_menu" layout="@layout/include_nav"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content" />
android:choiceMode="singleChoice" </ScrollView>
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

@ -18,14 +18,10 @@
android:layout_gravity="start" android:layout_gravity="start"
android:background="?attr/colorDrawerBackground"> android:background="?attr/colorDrawerBackground">
<ListView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/drawer_menu" android:id="@+id/rvMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -1,5 +1,4 @@
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -11,22 +10,17 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout <ScrollView
android:id="@+id/drawer_container" android:id="@+id/drawer_container"
android:layout_width="270dp" android:layout_width="270dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:background="?attr/colorDrawerBackground"> android:background="?attr/colorDrawerBackground"
android:orientation="vertical">
<ListView <include
android:id="@+id/drawer_menu" layout="@layout/include_nav"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content" />
android:choiceMode="singleChoice" </ScrollView>
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

@ -76,6 +76,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbHide" /> app:layout_constraintTop_toBottomOf="@id/cbHide" />
<CheckBox
android:id="@+id/cbNavigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_navigation_folder"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbUnified" />
<CheckBox <CheckBox
android:id="@+id/cbNotify" android:id="@+id/cbNotify"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -83,7 +92,7 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:text="@string/title_notify_folder" android:text="@string/title_notify_folder"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbUnified" /> app:layout_constraintTop_toBottomOf="@id/cbNavigation" />
<CheckBox <CheckBox
android:id="@+id/cbSynchronize" android:id="@+id/cbSynchronize"

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/vSeparatorAccount"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvAccount" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorAccount" />
<View
android:id="@+id/vSeparatorFolder"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvFolder" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorFolder" />
<View
android:id="@+id/vSeparatorMenu"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvMenu" />
<ImageView
android:id="@+id/ivExpander"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/title_legend_expander"
android:src="@drawable/expander"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorMenu" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMenuExtra1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ivExpander" />
<View
android:id="@+id/vSeparatorMenuExtra"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvMenuExtra1" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMenuExtra2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorMenuExtra" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:gravity="center_vertical"
android:src="@drawable/baseline_mail_outline_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvItem"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="start"
android:gravity="center_vertical"
android:minHeight="48dp"
android:singleLine="true"
android:text="Menu item"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivItem"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivExpander"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:contentDescription="@string/title_legend_expander"
android:src="@drawable/expander"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<View
android:id="@+id/vSeparator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clItem"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ivItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:gravity="center_vertical"
android:src="@drawable/baseline_mail_outline_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvItem"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:ellipsize="start"
android:gravity="center_vertical"
android:minHeight="48dp"
android:singleLine="true"
android:text="Nav item"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivItem"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -314,6 +314,7 @@
<string name="title_show_folders">Show hidden folders</string> <string name="title_show_folders">Show hidden folders</string>
<string name="title_hide_folder">Hide folder</string> <string name="title_hide_folder">Hide folder</string>
<string name="title_unified_folder">Show in unified inbox</string> <string name="title_unified_folder">Show in unified inbox</string>
<string name="title_navigation_folder">Show in navigation menu</string>
<string name="title_synchronize_folder">Synchronize (receive messages)</string> <string name="title_synchronize_folder">Synchronize (receive messages)</string>
<string name="title_poll_folder">Check periodically instead of continuous synchronize</string> <string name="title_poll_folder">Check periodically instead of continuous synchronize</string>
<string name="title_download_folder">Automatically download message texts and attachments</string> <string name="title_download_folder">Automatically download message texts and attachments</string>

Loading…
Cancel
Save