From 5754fcf44541b958a20caa5e0df29af42f87cb9c Mon Sep 17 00:00:00 2001 From: Andrew Ridgway Date: Tue, 4 Aug 2026 23:49:37 +1000 Subject: [PATCH] design: modernize Material 3 UI with drawer navigation and richer teal theme Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../com/example/resbuilder/ui/MainActivity.kt | 121 +++---- .../main/res/drawable/bg_gradient_primary.xml | 3 +- .../drawable/bg_gradient_primary_bottom.xml | 9 + app/src/main/res/drawable/ic_document.xml | 10 + .../main/res/drawable/ic_menu_hamburger.xml | 11 + app/src/main/res/drawable/ic_sparkle.xml | 10 + app/src/main/res/layout/activity_login.xml | 51 ++- app/src/main/res/layout/activity_main.xml | 121 +++---- app/src/main/res/layout/fragment_job_form.xml | 340 +++++++++++------- .../main/res/layout/fragment_job_status.xml | 52 +-- .../main/res/layout/fragment_my_resumes.xml | 58 ++- app/src/main/res/layout/fragment_result.xml | 72 ++-- app/src/main/res/layout/item_job_summary.xml | 77 ++-- app/src/main/res/layout/nav_header.xml | 49 +++ app/src/main/res/menu/menu_main.xml | 13 - app/src/main/res/menu/menu_nav_drawer.xml | 39 ++ app/src/main/res/values-night/themes.xml | 64 ++-- app/src/main/res/values/colors.xml | 65 ++-- app/src/main/res/values/dimens.xml | 18 + app/src/main/res/values/strings.xml | 4 + app/src/main/res/values/themes.xml | 7 +- 21 files changed, 738 insertions(+), 456 deletions(-) create mode 100644 app/src/main/res/drawable/bg_gradient_primary_bottom.xml create mode 100644 app/src/main/res/drawable/ic_document.xml create mode 100644 app/src/main/res/drawable/ic_menu_hamburger.xml create mode 100644 app/src/main/res/drawable/ic_sparkle.xml create mode 100644 app/src/main/res/layout/nav_header.xml delete mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/menu/menu_nav_drawer.xml create mode 100644 app/src/main/res/values/dimens.xml diff --git a/app/src/main/java/com/example/resbuilder/ui/MainActivity.kt b/app/src/main/java/com/example/resbuilder/ui/MainActivity.kt index 3883f63..a145023 100644 --- a/app/src/main/java/com/example/resbuilder/ui/MainActivity.kt +++ b/app/src/main/java/com/example/resbuilder/ui/MainActivity.kt @@ -5,7 +5,6 @@ import android.graphics.PorterDuff import android.os.Bundle import android.os.Handler import android.os.Looper -import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.ImageView @@ -13,18 +12,21 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.drawerlayout.widget.DrawerLayout import com.example.resbuilder.R import com.example.resbuilder.data.model.User import com.example.resbuilder.data.remote.ApiClient import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.tabs.TabLayout +import com.google.android.material.navigation.NavigationView class MainActivity : AppCompatActivity(), JobFormFragment.JobFormListener, ResultFragment.OnNewListener { + private lateinit var drawerLayout: DrawerLayout private lateinit var toolbar: MaterialToolbar - private lateinit var tabLayout: TabLayout + private lateinit var navView: NavigationView private lateinit var healthIndicator: ImageView private lateinit var userName: TextView + private lateinit var userEmail: TextView private lateinit var usageBanner: TextView private val handler = Handler(Looper.getMainLooper()) @@ -32,43 +34,65 @@ class MainActivity : AppCompatActivity(), JobFormFragment.JobFormListener, Resul private var healthRunnable: Runnable? = null companion object { - private const val TAB_RESUME = 0 - private const val TAB_COVER = 1 + private const val NAV_BUILD_RESUME = 0 + private const val NAV_BUILD_COVER_LETTER = 1 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + drawerLayout = findViewById(R.id.drawerLayout) toolbar = findViewById(R.id.toolbar) - tabLayout = findViewById(R.id.tabLayout) - healthIndicator = findViewById(R.id.healthIndicator) - userName = findViewById(R.id.userName) + navView = findViewById(R.id.navView) usageBanner = findViewById(R.id.usageBanner) + val header = navView.getHeaderView(0) + healthIndicator = header.findViewById(R.id.healthIndicator) + userName = header.findViewById(R.id.userName) + userEmail = header.findViewById(R.id.userEmail) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_menu_hamburger) - tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_build_resume)) - tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_build_cover_letter)) - - tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab?) { - switchFormTab(tab?.position ?: TAB_RESUME) - } - - override fun onTabUnselected(tab: TabLayout.Tab?) {} - override fun onTabReselected(tab: TabLayout.Tab?) { - switchFormTab(tab?.position ?: TAB_RESUME) - } - }) + navView.setNavigationItemSelectedListener { item -> + drawerLayout.closeDrawers() + handleNavItem(item.itemId) + true + } loadUser() startHealthPolling() if (savedInstanceState == null) { - switchFormTab(TAB_RESUME) + showBuildForm(NAV_BUILD_RESUME) } } + private fun handleNavItem(itemId: Int) { + when (itemId) { + R.id.navBuildResume -> showBuildForm(NAV_BUILD_RESUME) + R.id.navBuildCoverLetter -> showBuildForm(NAV_BUILD_COVER_LETTER) + R.id.navMyResumes -> showMyResumes() + R.id.navAdmin -> startActivity(Intent(this, AdminActivity::class.java)) + R.id.navSignOut -> signOut() + } + } + + private fun showBuildForm(position: Int) { + val type = if (position == NAV_BUILD_COVER_LETTER) JobFormFragment.TYPE_COVER_LETTER else JobFormFragment.TYPE_RESUME + supportFragmentManager.beginTransaction() + .replace(R.id.fragmentContainer, JobFormFragment.newInstance(type)) + .commitAllowingStateLoss() + } + + private fun showMyResumes() { + supportFragmentManager.beginTransaction() + .replace(R.id.fragmentContainer, MyResumesFragment()) + .addToBackStack(null) + .commitAllowingStateLoss() + } + private fun loadUser() { Thread { try { @@ -86,20 +110,13 @@ class MainActivity : AppCompatActivity(), JobFormFragment.JobFormListener, Resul private fun updateUserUI() { userName.text = user.name ?: user.email ?: getString(R.string.unknown_user) - if (!user.authenticated || (!user.is_admin && !user.can_submit)) { - usageBanner.visibility = View.VISIBLE + userEmail.text = user.email ?: getString(R.string.unknown_email) + usageBanner.visibility = if (!user.authenticated || (!user.is_admin && !user.can_submit)) { + View.VISIBLE } else { - usageBanner.visibility = View.GONE + View.GONE } - invalidateOptionsMenu() - } - - private fun switchFormTab(position: Int) { - val type = if (position == TAB_COVER) JobFormFragment.TYPE_COVER_LETTER else JobFormFragment.TYPE_RESUME - val fragment = JobFormFragment.newInstance(type) - supportFragmentManager.beginTransaction() - .replace(R.id.fragmentContainer, fragment) - .commitAllowingStateLoss() + navView.menu.findItem(R.id.navAdmin)?.isVisible = user.is_admin } private fun startHealthPolling() { @@ -130,34 +147,12 @@ class MainActivity : AppCompatActivity(), JobFormFragment.JobFormListener, Resul }.start() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - override fun onPrepareOptionsMenu(menu: Menu?): Boolean { - menu?.findItem(R.id.menuAdminPanel)?.isVisible = user.is_admin - return super.onPrepareOptionsMenu(menu) - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.menuMyResumes -> { - supportFragmentManager.beginTransaction() - .replace(R.id.fragmentContainer, MyResumesFragment()) - .addToBackStack(null) - .commitAllowingStateLoss() - true - } - R.id.menuSignOut -> { - signOut() - true - } - R.id.menuAdminPanel -> { - startActivity(Intent(this, AdminActivity::class.java)) - true - } - else -> super.onOptionsItemSelected(item) + return if (item.itemId == android.R.id.home) { + drawerLayout.openDrawer(navView) + true + } else { + super.onOptionsItemSelected(item) } } @@ -183,8 +178,8 @@ class MainActivity : AppCompatActivity(), JobFormFragment.JobFormListener, Resul fun onResultNewJob() { supportFragmentManager.popBackStackImmediate() - tabLayout.getTabAt(TAB_RESUME)?.select() - switchFormTab(TAB_RESUME) + navView.menu.getItem(NAV_BUILD_RESUME)?.isChecked = true + showBuildForm(NAV_BUILD_RESUME) } override fun onNewJob() { diff --git a/app/src/main/res/drawable/bg_gradient_primary.xml b/app/src/main/res/drawable/bg_gradient_primary.xml index f405319..ed6da10 100644 --- a/app/src/main/res/drawable/bg_gradient_primary.xml +++ b/app/src/main/res/drawable/bg_gradient_primary.xml @@ -1,8 +1,9 @@ diff --git a/app/src/main/res/drawable/bg_gradient_primary_bottom.xml b/app/src/main/res/drawable/bg_gradient_primary_bottom.xml new file mode 100644 index 0000000..db08bea --- /dev/null +++ b/app/src/main/res/drawable/bg_gradient_primary_bottom.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_document.xml b/app/src/main/res/drawable/ic_document.xml new file mode 100644 index 0000000..5039c59 --- /dev/null +++ b/app/src/main/res/drawable/ic_document.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_hamburger.xml b/app/src/main/res/drawable/ic_menu_hamburger.xml new file mode 100644 index 0000000..bbe3fff --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_hamburger.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ic_sparkle.xml b/app/src/main/res/drawable/ic_sparkle.xml new file mode 100644 index 0000000..f1565a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_sparkle.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 7308c05..74d3ffb 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -7,7 +7,7 @@ android:background="?attr/colorSurface" tools:context=".ui.LoginActivity"> - + + + + app:layout_constraintGuide_percent="0.40" /> + android:padding="@dimen/space_xl"> - + + app:cardCornerRadius="@dimen/card_radius_large" + app:cardElevation="0dp" + app:strokeWidth="0dp"> @@ -71,7 +83,7 @@ android:id="@+id/loginTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="24dp" + android:layout_marginTop="@dimen/space_lg" android:text="@string/app_title" android:textAlignment="center" android:textAppearance="?attr/textAppearanceHeadlineMedium" @@ -83,7 +95,7 @@ android:id="@+id/loginSubtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="8dp" + android:layout_marginTop="@dimen/space_sm" android:text="@string/welcome_subtitle" android:textAlignment="center" android:textAppearance="?attr/textAppearanceBodyLarge" @@ -93,16 +105,17 @@ - + - - + + android:layout_height="match_parent" + android:orientation="vertical"> - + + app:elevation="0dp"> - - - - - - - + + - - + android:layout_height="match_parent" + android:background="?attr/colorSurface" /> + - + + android:layout_gravity="start" + android:background="?attr/colorSurfaceContainerLowest" + app:headerLayout="@layout/nav_header" + app:itemHorizontalPadding="@dimen/space_md" + app:itemIconPadding="@dimen/space_md" + app:itemIconTint="?attr/colorOnSurfaceVariant" + app:itemRippleColor="?attr/colorPrimaryContainer" + app:itemTextAppearance="?attr/textAppearanceBodyLarge" + app:itemTextColor="?attr/colorOnSurface" + app:itemVerticalPadding="@dimen/space_md" + app:menu="@menu/menu_nav_drawer" /> - + diff --git a/app/src/main/res/layout/fragment_job_form.xml b/app/src/main/res/layout/fragment_job_form.xml index e05c1e5..99cefd3 100644 --- a/app/src/main/res/layout/fragment_job_form.xml +++ b/app/src/main/res/layout/fragment_job_form.xml @@ -6,182 +6,244 @@ android:layout_height="match_parent" android:background="?attr/colorSurface" android:fillViewport="true" + android:clipToPadding="false" + android:paddingBottom="@dimen/space_md" tools:context=".ui.JobFormFragment"> + android:orientation="vertical"> - + + android:background="@drawable/bg_gradient_primary" + android:minHeight="@dimen/hero_header_min_height" + android:padding="@dimen/space_lg"> + + + android:layout_gravity="bottom|start" + android:orientation="vertical"> + + - - - - - - - - - - - - - - - - - - - - + android:textAppearance="?attr/textAppearanceHeadlineMedium" + android:textColor="?attr/colorOnPrimary" /> - + - + android:orientation="vertical" + android:padding="@dimen/space_md"> - + + app:cardBackgroundColor="?attr/colorSurfaceContainerLow" + app:cardCornerRadius="@dimen/card_radius" + app:cardElevation="0dp" + app:strokeColor="?attr/colorOutlineVariant" + app:strokeWidth="1dp"> + android:orientation="vertical" + android:padding="@dimen/space_md"> - - - - - - - - - + android:layout_marginTop="@dimen/space_sm" + android:hint="@string/hint_job_title" + style="@style/Widget.Material3.TextInputLayout.FilledBox" + app:boxBackgroundColor="?attr/colorSurfaceContainerHighest" + app:boxStrokeColor="?attr/colorPrimary" + app:cursorColor="?attr/colorPrimary" + app:hintTextColor="?attr/colorOnSurfaceVariant"> - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_job_status.xml b/app/src/main/res/layout/fragment_job_status.xml index 8aa4818..0196d29 100644 --- a/app/src/main/res/layout/fragment_job_status.xml +++ b/app/src/main/res/layout/fragment_job_status.xml @@ -7,14 +7,15 @@ android:background="?attr/colorSurface" android:gravity="center" android:orientation="vertical" - android:padding="16dp"> + android:padding="@dimen/space_md"> @@ -24,27 +25,36 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" - android:padding="24dp"> + android:padding="@dimen/space_lg"> - + + app:cardBackgroundColor="?attr/colorPrimaryContainer" + app:cardCornerRadius="16dp" + app:cardElevation="0dp" + app:strokeWidth="0dp"> + + + diff --git a/app/src/main/res/layout/fragment_my_resumes.xml b/app/src/main/res/layout/fragment_my_resumes.xml index 56b7c7d..b0ae36a 100644 --- a/app/src/main/res/layout/fragment_my_resumes.xml +++ b/app/src/main/res/layout/fragment_my_resumes.xml @@ -5,24 +5,55 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/colorSurface" - android:orientation="vertical" - android:padding="16dp"> + android:orientation="vertical"> - + + android:background="@drawable/bg_gradient_primary" + android:minHeight="@dimen/hero_header_min_height" + android:padding="@dimen/space_lg"> + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 6c3463b..e516be7 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -8,13 +8,15 @@ android:orientation="vertical" tools:context=".ui.ResultFragment"> + @@ -23,23 +25,31 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" - android:minHeight="48dp" + android:minHeight="@dimen/touch_target_min" android:orientation="horizontal" - android:padding="12dp"> + android:padding="@dimen/space_sm"> - + app:cardBackgroundColor="?attr/colorSuccessContainer" + app:cardCornerRadius="16dp" + app:cardElevation="0dp" + app:strokeWidth="0dp"> + + + @@ -59,8 +69,8 @@ android:id="@+id/newButton" style="@style/Widget.Material3.Button.TextButton" android:layout_width="wrap_content" - android:layout_height="48dp" - android:layout_marginStart="4dp" + android:layout_height="@dimen/button_height" + android:layout_marginStart="@dimen/space_xs" android:contentDescription="@string/cd_new_job" android:minWidth="64dp" android:text="@string/new_job" /> @@ -71,17 +81,19 @@ android:id="@+id/resultWebView" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginHorizontal="16dp" + android:layout_marginHorizontal="@dimen/space_md" android:layout_weight="1" android:contentDescription="@string/result_preview" /> + @@ -90,14 +102,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:padding="12dp"> + android:padding="@dimen/space_sm"> @@ -106,8 +118,8 @@ android:id="@+id/docxButton" style="@style/Widget.Material3.Button.OutlinedButton" android:layout_width="0dp" - android:layout_height="48dp" - android:layout_marginStart="8dp" + android:layout_height="@dimen/button_height" + android:layout_marginStart="@dimen/space_sm" android:layout_weight="1" android:contentDescription="@string/cd_export_docx" android:text="@string/export_docx" /> diff --git a/app/src/main/res/layout/item_job_summary.xml b/app/src/main/res/layout/item_job_summary.xml index 5d15a3a..c506f06 100644 --- a/app/src/main/res/layout/item_job_summary.xml +++ b/app/src/main/res/layout/item_job_summary.xml @@ -4,36 +4,73 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginVertical="6dp" - app:cardCornerRadius="12dp" + android:layout_marginVertical="@dimen/space_sm" + app:cardBackgroundColor="?attr/colorSurfaceContainerLow" + app:cardCornerRadius="@dimen/card_radius" app:cardElevation="0dp" + app:rippleColor="?attr/colorPrimaryContainer" app:strokeColor="?attr/colorOutlineVariant" app:strokeWidth="1dp"> + android:gravity="center_vertical" + android:minHeight="@dimen/touch_target_min" + android:orientation="horizontal" + android:padding="@dimen/space_md"> - + - + + + + android:layout_marginStart="@dimen/space_md" + android:layout_weight="1" + android:orientation="vertical"> + + + + + + + diff --git a/app/src/main/res/layout/nav_header.xml b/app/src/main/res/layout/nav_header.xml new file mode 100644 index 0000000..b592fb2 --- /dev/null +++ b/app/src/main/res/layout/nav_header.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml deleted file mode 100644 index 64943dc..0000000 --- a/app/src/main/res/menu/menu_main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/app/src/main/res/menu/menu_nav_drawer.xml b/app/src/main/res/menu/menu_nav_drawer.xml new file mode 100644 index 0000000..926b79a --- /dev/null +++ b/app/src/main/res/menu/menu_nav_drawer.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 7c10538..6d8acf2 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,41 +1,43 @@