Fix error reporting and clean up some of the code. Also fixed missing toolbar on settings page

This commit is contained in:
Billy Brawner 2019-05-18 10:02:26 -07:00 committed by William Brawner
parent f9fd55b369
commit b5b850c605
14 changed files with 175 additions and 97 deletions

View file

@ -1,5 +1,7 @@
package com.wbrawner.simplemarkdown; package com.wbrawner.simplemarkdown;
import android.content.Context;
import com.wbrawner.simplemarkdown.view.activity.MainActivity; import com.wbrawner.simplemarkdown.view.activity.MainActivity;
import com.wbrawner.simplemarkdown.view.activity.SplashActivity; import com.wbrawner.simplemarkdown.view.activity.SplashActivity;
import com.wbrawner.simplemarkdown.view.fragment.EditFragment; import com.wbrawner.simplemarkdown.view.fragment.EditFragment;
@ -7,6 +9,7 @@ import com.wbrawner.simplemarkdown.view.fragment.PreviewFragment;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.BindsInstance;
import dagger.Component; import dagger.Component;
/** /**
@ -21,4 +24,12 @@ public interface AppComponent {
void inject(SplashActivity activity); void inject(SplashActivity activity);
void inject(EditFragment fragment); void inject(EditFragment fragment);
void inject(PreviewFragment fragment); void inject(PreviewFragment fragment);
@Component.Builder
abstract class Builder {
@BindsInstance
abstract Builder context(Context context);
abstract AppComponent build();
}
} }

View file

@ -1,10 +1,9 @@
package com.wbrawner.simplemarkdown; package com.wbrawner.simplemarkdown;
import android.content.Context;
import com.wbrawner.simplemarkdown.model.MarkdownFile;
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter; import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter;
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl; import com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl;
import com.wbrawner.simplemarkdown.utility.CrashlyticsErrorHandler;
import com.wbrawner.simplemarkdown.utility.ErrorHandler;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -17,18 +16,14 @@ import dagger.Provides;
@Module @Module
public class AppModule { public class AppModule {
private final Context context;
public AppModule(Context context) {
this.context = context;
}
@Provides
public MarkdownFile provideMarkdownFile() {
return new MarkdownFile();
}
@Provides @Singleton @Provides @Singleton
public MarkdownPresenter provideMarkdownPresenter(MarkdownFile file) { public MarkdownPresenter provideMarkdownPresenter(ErrorHandler errorHandler) {
return new MarkdownPresenterImpl(context.getApplicationContext(), file); return new MarkdownPresenterImpl(errorHandler);
}
@Provides
@Singleton
ErrorHandler provideErrorHandler() {
return new CrashlyticsErrorHandler();
} }
} }

View file

@ -10,7 +10,7 @@ public class MarkdownApplication extends MultiDexApplication {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
component = DaggerAppComponent.builder() component = DaggerAppComponent.builder()
.appModule(new AppModule(this)) .context(this)
.build(); .build();
} }

View file

@ -1,6 +1,6 @@
package com.wbrawner.simplemarkdown.model; package com.wbrawner.simplemarkdown.model;
import com.crashlytics.android.Crashlytics; import com.wbrawner.simplemarkdown.utility.ErrorHandler;
import com.wbrawner.simplemarkdown.utility.Utils; import com.wbrawner.simplemarkdown.utility.Utils;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -17,14 +17,17 @@ import java.io.OutputStreamWriter;
public class MarkdownFile { public class MarkdownFile {
private String name; private String name;
private String content; private String content;
private final ErrorHandler errorHandler;
public MarkdownFile(String name, String content) { public MarkdownFile(ErrorHandler errorHandler, String name, String content) {
this.errorHandler = errorHandler;
this.name = name; this.name = name;
this.content = content; this.content = content;
} }
public MarkdownFile() { public MarkdownFile(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
this.name = "Untitled.md"; this.name = "Untitled.md";
this.content = ""; this.content = "";
} }
@ -57,8 +60,7 @@ public class MarkdownFile {
this.name = name; this.name = name;
this.content = sb.toString(); this.content = sb.toString();
return true; return true;
} catch (IOException e) { } catch (IOException ignored) {
Crashlytics.logException(e);
return false; return false;
} finally { } finally {
Utils.closeQuietly(reader); Utils.closeQuietly(reader);
@ -71,17 +73,10 @@ public class MarkdownFile {
writer = new OutputStreamWriter(outputStream); writer = new OutputStreamWriter(outputStream);
writer.write(this.content); writer.write(this.content);
this.name = name; this.name = name;
} catch (IOException e) { } catch (IOException ignored) {
Crashlytics.logException(e);
return false; return false;
} finally { } finally {
if (writer != null) { Utils.closeQuietly(writer);
try {
writer.close();
} catch (IOException e) {
Crashlytics.logException(e);
}
}
} }
return true; return true;
} }

View file

@ -7,8 +7,8 @@ import android.os.Handler;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import com.commonsware.cwac.anddown.AndDown; import com.commonsware.cwac.anddown.AndDown;
import com.crashlytics.android.Crashlytics;
import com.wbrawner.simplemarkdown.model.MarkdownFile; import com.wbrawner.simplemarkdown.model.MarkdownFile;
import com.wbrawner.simplemarkdown.utility.ErrorHandler;
import com.wbrawner.simplemarkdown.utility.Utils; import com.wbrawner.simplemarkdown.utility.Utils;
import com.wbrawner.simplemarkdown.view.MarkdownEditView; import com.wbrawner.simplemarkdown.view.MarkdownEditView;
import com.wbrawner.simplemarkdown.view.MarkdownPreviewView; import com.wbrawner.simplemarkdown.view.MarkdownPreviewView;
@ -16,16 +16,23 @@ import com.wbrawner.simplemarkdown.view.MarkdownPreviewView;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class MarkdownPresenterImpl implements MarkdownPresenter { public class MarkdownPresenterImpl implements MarkdownPresenter {
private final Object fileLock = new Object(); private final Object fileLock = new Object();
private MarkdownFile file; private MarkdownFile file;
private volatile MarkdownEditView editView; private volatile MarkdownEditView editView;
private volatile MarkdownPreviewView previewView; private volatile MarkdownPreviewView previewView;
private Handler fileHandler = new Handler(); private Handler fileHandler = new Handler();
private final ErrorHandler errorHandler;
public MarkdownPresenterImpl(Context context, MarkdownFile file) { @Inject
public MarkdownPresenterImpl(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
synchronized (fileLock) { synchronized (fileLock) {
this.file = file; this.file = new MarkdownFile(errorHandler);
} }
} }
@ -41,7 +48,7 @@ public class MarkdownPresenterImpl implements MarkdownPresenter {
final OnTempFileLoadedListener listener final OnTempFileLoadedListener listener
) { ) {
Runnable fileLoader = () -> { Runnable fileLoader = () -> {
MarkdownFile tmpFile = new MarkdownFile(); MarkdownFile tmpFile = new MarkdownFile(errorHandler);
if (tmpFile.load(fileName, in)) { if (tmpFile.load(fileName, in)) {
if (listener != null) { if (listener != null) {
String html = generateHTML(tmpFile.getContent()); String html = generateHTML(tmpFile.getContent());
@ -76,7 +83,7 @@ public class MarkdownPresenterImpl implements MarkdownPresenter {
currentEditView.setTitle(newName); currentEditView.setTitle(newName);
currentEditView.setMarkdown(""); currentEditView.setMarkdown("");
} }
file = new MarkdownFile(newName, ""); file = new MarkdownFile(errorHandler, newName, "");
} }
} }
@ -194,7 +201,7 @@ public class MarkdownPresenterImpl implements MarkdownPresenter {
} }
loadMarkdown(fileName, in); loadMarkdown(fileName, in);
} catch (Exception e) { } catch (Exception e) {
Crashlytics.logException(e); errorHandler.reportException(e);
MarkdownEditView currentEditView = editView; MarkdownEditView currentEditView = editView;
if (currentEditView != null) { if (currentEditView != null) {
currentEditView.onFileLoaded(false); currentEditView.onFileLoaded(false);

View file

@ -6,7 +6,7 @@ public class Constants {
// Request codes // Request codes
public static final int REQUEST_OPEN_FILE = 1; public static final int REQUEST_OPEN_FILE = 1;
public static final int REQUEST_SAVE_FILE = 2; public static final int REQUEST_SAVE_FILE = 2;
public static final int REQUEST_ROOT_DIR = 1000; public static final int REQUEST_DARK_MODE = 3;
// Extras // Extras
public static final String EXTRA_FILE = "EXTRA_FILE"; public static final String EXTRA_FILE = "EXTRA_FILE";

View file

@ -0,0 +1,27 @@
package com.wbrawner.simplemarkdown.utility
import android.content.Context
import com.crashlytics.android.Crashlytics
import io.fabric.sdk.android.BuildConfig
import io.fabric.sdk.android.Fabric
import java.util.concurrent.atomic.AtomicBoolean
interface ErrorHandler {
fun init(context: Context)
fun reportException(t: Throwable)
}
class CrashlyticsErrorHandler : ErrorHandler {
private val isInitialized = AtomicBoolean(false)
override fun init(context: Context) {
if (!isInitialized.getAndSet(true)) {
Fabric.with(context, Crashlytics())
}
}
override fun reportException(t: Throwable) {
if (!isInitialized.get() || BuildConfig.DEBUG) return
Crashlytics.logException(t)
}
}

View file

@ -11,8 +11,6 @@ import android.preference.PreferenceManager;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.crashlytics.android.Crashlytics;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -101,11 +99,11 @@ public class Utils {
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
public static Handler createSafeHandler(String name) { public static Handler createSafeHandler(ErrorHandler errorHandler, String name) {
HandlerThread handlerThread = new HandlerThread(name); HandlerThread handlerThread = new HandlerThread(name);
handlerThread.start(); handlerThread.start();
handlerThread.setUncaughtExceptionHandler((t, e) -> { handlerThread.setUncaughtExceptionHandler((t, e) -> {
Crashlytics.logException(e); errorHandler.reportException(e);
t.interrupt(); t.interrupt();
}); });
return new Handler(handlerThread.getLooper()); return new Handler(handlerThread.getLooper());

View file

@ -11,14 +11,16 @@ import android.provider.OpenableColumns
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import com.crashlytics.android.Crashlytics
import com.wbrawner.simplemarkdown.MarkdownApplication import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter
import com.wbrawner.simplemarkdown.utility.Constants import com.wbrawner.simplemarkdown.utility.Constants
import com.wbrawner.simplemarkdown.utility.Constants.REQUEST_DARK_MODE
import com.wbrawner.simplemarkdown.utility.ErrorHandler
import com.wbrawner.simplemarkdown.utility.Utils import com.wbrawner.simplemarkdown.utility.Utils
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
@ -30,7 +32,8 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
@Inject @Inject
lateinit var presenter: MarkdownPresenter lateinit var presenter: MarkdownPresenter
@Inject
lateinit var errorHandler: ErrorHandler
private var shouldAutoSave = true private var shouldAutoSave = true
private var newFileHandler: NewFileHandler? = null private var newFileHandler: NewFileHandler? = null
@ -54,9 +57,6 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
tabLayout!!.visibility = View.GONE tabLayout!!.visibility = View.GONE
} }
newFileHandler = NewFileHandler() newFileHandler = NewFileHandler()
if (intent.getBooleanExtra(Constants.EXTRA_EXPLORER, false)) {
requestFileOp(Constants.REQUEST_OPEN_FILE)
}
} }
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
@ -100,7 +100,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
R.id.action_help -> showInfoActivity(R.id.action_help) R.id.action_help -> showInfoActivity(R.id.action_help)
R.id.action_settings -> { R.id.action_settings -> {
val settingsIntent = Intent(this@MainActivity, SettingsActivity::class.java) val settingsIntent = Intent(this@MainActivity, SettingsActivity::class.java)
startActivity(settingsIntent) startActivityForResult(settingsIntent, REQUEST_DARK_MODE)
} }
R.id.action_libraries -> showInfoActivity(R.id.action_libraries) R.id.action_libraries -> showInfoActivity(R.id.action_libraries)
R.id.action_privacy -> showInfoActivity(R.id.action_privacy) R.id.action_privacy -> showInfoActivity(R.id.action_privacy)
@ -145,7 +145,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
} }
}) })
} catch (e: Exception) { } catch (e: Exception) {
Crashlytics.logException(e) errorHandler.reportException(e)
Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT).show() Toast.makeText(this@MainActivity, R.string.file_load_error, Toast.LENGTH_SHORT).show()
} }
@ -185,10 +185,6 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
return return
} }
val mimeType: String? = data.data?.let { returnUri ->
contentResolver.getType(returnUri)
}
val fileName = contentResolver.query(data.data!!, null, null, null, null) val fileName = contentResolver.query(data.data!!, null, null, null, null)
?.use { cursor -> ?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
@ -220,6 +216,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
contentResolver.openOutputStream(data.data!!) contentResolver.openOutputStream(data.data!!)
) )
} }
REQUEST_DARK_MODE -> recreate()
} }
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
} }
@ -245,9 +242,13 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
Constants.REQUEST_OPEN_FILE -> { Constants.REQUEST_OPEN_FILE -> {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
type = "*/*" type = "*/*"
if (MimeTypeMap.getSingleton().hasMimeType("md")) {
// If the device doesn't recognize markdown files then we're not going to be
// able to open them at all, so there's no sense in filtering them out.
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/plain", "text/markdown")) putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/plain", "text/markdown"))
} }
} }
}
else -> null else -> null
} ?: return } ?: return
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)

View file

@ -14,6 +14,7 @@ public class SettingsActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings); setContentView(R.layout.activity_settings);
setSupportActionBar(findViewById(R.id.toolbar));
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
@ -21,11 +22,9 @@ public class SettingsActivity extends AppCompatActivity {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { // The only menu item is the back button
case android.R.id.home: setResult(RESULT_OK);
onBackPressed(); finish();
return true; return true;
} }
return super.onOptionsItemSelected(item);
}
} }

View file

@ -10,31 +10,30 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import com.crashlytics.android.Crashlytics;
import com.wbrawner.simplemarkdown.BuildConfig;
import com.wbrawner.simplemarkdown.MarkdownApplication; import com.wbrawner.simplemarkdown.MarkdownApplication;
import com.wbrawner.simplemarkdown.R; import com.wbrawner.simplemarkdown.R;
import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter; import com.wbrawner.simplemarkdown.presentation.MarkdownPresenter;
import com.wbrawner.simplemarkdown.utility.ErrorHandler;
import com.wbrawner.simplemarkdown.utility.Utils; import com.wbrawner.simplemarkdown.utility.Utils;
import javax.inject.Inject; import javax.inject.Inject;
import io.fabric.sdk.android.Fabric;
public class SplashActivity extends AppCompatActivity { public class SplashActivity extends AppCompatActivity {
@Inject @Inject
MarkdownPresenter presenter; MarkdownPresenter presenter;
@Inject
ErrorHandler errorHandler;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
if (sharedPreferences.getBoolean(getString(R.string.error_reports_enabled), true)
&& !BuildConfig.DEBUG) {
Fabric.with(this, new Crashlytics());
}
((MarkdownApplication) getApplication()).getComponent().inject(this); ((MarkdownApplication) getApplication()).getComponent().inject(this);
if (sharedPreferences.getBoolean(getString(R.string.error_reports_enabled), true)) {
errorHandler.init(this);
}
String darkModeValue = sharedPreferences.getString( String darkModeValue = sharedPreferences.getString(
getString(R.string.pref_key_dark_mode), getString(R.string.pref_key_dark_mode),

View file

@ -9,6 +9,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.WebView import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.wbrawner.simplemarkdown.BuildConfig import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.MarkdownApplication import com.wbrawner.simplemarkdown.MarkdownApplication
@ -51,7 +52,9 @@ class PreviewFragment : Fragment(), MarkdownPreviewView {
return@post return@post
} }
val isNightMode = context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
AppCompatDelegate.MODE_NIGHT_YES
|| context!!.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
val defaultCssId = if (isNightMode) { val defaultCssId = if (isNightMode) {
R.string.pref_custom_css_default_dark R.string.pref_custom_css_default_dark
} else { } else {

View file

@ -14,10 +14,28 @@
android:clipChildren="false" android:clipChildren="false"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="90dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tabLayout" /> app:layout_constraintBottom_toBottomOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:translationY="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp">
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout" android:id="@+id/tabLayout"
@ -48,10 +66,9 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:clipChildren="false" android:clipChildren="false"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:clipToPadding="false" android:clipToPadding="false" />
app:layout_constraintStart_toStartOf="parent" </LinearLayout>
app:layout_constraintEnd_toEndOf="parent" </com.google.android.material.card.MaterialCardView>
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,12 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<fragment <fragment
android:id="@+id/fragment_settings" android:id="@+id/fragment_settings"
class="com.wbrawner.simplemarkdown.view.fragment.SettingsFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
class="com.wbrawner.simplemarkdown.view.fragment.SettingsFragment" /> android:layout_marginBottom="90dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> <com.google.android.material.card.MaterialCardView
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:translationY="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorBackground"
android:layout_marginBottom="16dp" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>