From 3617a8ee07be8a67424cc23b29ec33234ae3514b Mon Sep 17 00:00:00 2001 From: annaksenova Date: Wed, 7 Jan 2026 15:22:53 +0300 Subject: [PATCH] Homework --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 2 + .../ru/otus/cookbook/ui/CookbookFragment.kt | 33 +++-- .../cookbook/ui/DeleteRecipeDialogFragment.kt | 23 ++++ .../ru/otus/cookbook/ui/RecipeAdapter.kt | 76 +++++++++++ .../ru/otus/cookbook/ui/RecipeFragment.kt | 80 +++++++----- app/src/main/res/anim/slide_in_left.xml | 6 + app/src/main/res/anim/slide_in_right.xml | 6 + app/src/main/res/anim/slide_out_left.xml | 6 + app/src/main/res/anim/slide_out_right.xml | 6 + app/src/main/res/drawable/ic_back.xml | 9 ++ app/src/main/res/drawable/ic_close.xml | 9 ++ app/src/main/res/drawable/ic_delete.xml | 9 ++ .../res/drawable/ic_placeholder_cookbook.png | Bin 0 -> 9799 bytes .../res/drawable/ic_placeholder_shapes.png | Bin 0 -> 1210 bytes app/src/main/res/drawable/ic_search.xml | 9 ++ app/src/main/res/layout/activity_main.xml | 15 +-- app/src/main/res/layout/fragment_cookbook.xml | 60 ++++++++- app/src/main/res/layout/fragment_recipe.xml | 121 +++++++++++++++++- .../main/res/layout/vh_recipe_category.xml | 18 ++- app/src/main/res/layout/vh_recipe_item.xml | 95 +++++++++++++- app/src/main/res/navigation/navigation.xml | 41 ++++++ app/src/main/res/values-night/themes.xml | 4 - app/src/main/res/values/colors.xml | 5 +- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 5 + gradle/libs.versions.toml | 2 + webinar/src/main/res/values/colors.xml | 5 +- webinar/src/main/res/values/strings.xml | 2 - 29 files changed, 577 insertions(+), 72 deletions(-) create mode 100644 app/src/main/kotlin/ru/otus/cookbook/ui/DeleteRecipeDialogFragment.kt create mode 100644 app/src/main/kotlin/ru/otus/cookbook/ui/RecipeAdapter.kt create mode 100644 app/src/main/res/anim/slide_in_left.xml create mode 100644 app/src/main/res/anim/slide_in_right.xml create mode 100644 app/src/main/res/anim/slide_out_left.xml create mode 100644 app/src/main/res/anim/slide_out_right.xml create mode 100644 app/src/main/res/drawable/ic_back.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_delete.xml create mode 100644 app/src/main/res/drawable/ic_placeholder_cookbook.png create mode 100644 app/src/main/res/drawable/ic_placeholder_shapes.png create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/navigation/navigation.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f3207a6..3a7909e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,4 +50,5 @@ dependencies { implementation(libs.androidx.constraintlayout) testImplementation(libs.junit) testImplementation(libs.kotlin.coroutines.test) + implementation("io.coil-kt:coil:2.4.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 063f4d1..682aa63 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + (this) private val model: CookbookFragmentViewModel by viewModels { CookbookFragmentViewModel.Factory } + private var adapter: RecipeAdapter? = null + override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = binding.bind( - container, - FragmentCookbookBinding::inflate + container, FragmentCookbookBinding::inflate ) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + binding.withBinding { + btnClose.setOnClickListener { + requireActivity().finish() + } + } setupRecyclerView() viewLifecycleOwner.lifecycleScope.launch { - model.recipeList - .flowWithLifecycle(viewLifecycleOwner.lifecycle) + model.recipeList.flowWithLifecycle(viewLifecycleOwner.lifecycle) .collect(::onRecipeListUpdated) } } private fun setupRecyclerView() = binding.withBinding { - // Setup RecyclerView + adapter = RecipeAdapter { recipeId -> + val action = CookbookFragmentDirections.actionCookbookFragmentToRecipeFragment(recipeId) + findNavController().navigate(action) + } + recyclerView.layoutManager = LinearLayoutManager(context) + recyclerView.adapter = adapter } private fun onRecipeListUpdated(recipeList: List) { - // Handle recipe list + adapter?.items = recipeList + } + + override fun onDestroyView() { + super.onDestroyView() + adapter = null } } \ No newline at end of file diff --git a/app/src/main/kotlin/ru/otus/cookbook/ui/DeleteRecipeDialogFragment.kt b/app/src/main/kotlin/ru/otus/cookbook/ui/DeleteRecipeDialogFragment.kt new file mode 100644 index 0000000..0850a8f --- /dev/null +++ b/app/src/main/kotlin/ru/otus/cookbook/ui/DeleteRecipeDialogFragment.kt @@ -0,0 +1,23 @@ +package ru.otus.cookbook.ui + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import ru.otus.cookbook.R + +class DeleteRecipeDialogFragment : DialogFragment() { + + private val args: DeleteRecipeDialogFragmentArgs by navArgs() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext()).setTitle("Удалить рецепт?") + .setMessage("Вы уверены, что хотите удалить рецепт «${args.recipeTitle}»?") + .setPositiveButton("Удалить") { _, _ -> + findNavController().getBackStackEntry(R.id.recipeFragment).savedStateHandle["DELETE_CONFIRMED"] = + true + }.setNegativeButton("Отмена", null).create() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeAdapter.kt b/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeAdapter.kt new file mode 100644 index 0000000..c59d872 --- /dev/null +++ b/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeAdapter.kt @@ -0,0 +1,76 @@ +package ru.otus.cookbook.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import coil.load +import ru.otus.cookbook.R +import ru.otus.cookbook.data.RecipeListItem +import ru.otus.cookbook.databinding.VhRecipeCategoryBinding +import ru.otus.cookbook.databinding.VhRecipeItemBinding + +class RecipeAdapter(private val onRecipeClick: (Int) -> Unit) : + RecyclerView.Adapter() { + + var items: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + companion object { + private const val TYPE_CATEGORY = 0 + private const val TYPE_ITEM = 1 + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is RecipeListItem.CategoryItem -> TYPE_CATEGORY + is RecipeListItem.RecipeItem -> TYPE_ITEM + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = LayoutInflater.from(parent.context) + return if (viewType == TYPE_CATEGORY) { + CategoryViewHolder(VhRecipeCategoryBinding.inflate(inflater, parent, false)) + } else { + RecipeViewHolder(VhRecipeItemBinding.inflate(inflater, parent, false)) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val item = items[position] + when (holder) { + is CategoryViewHolder -> holder.bind(item as RecipeListItem.CategoryItem) + is RecipeViewHolder -> { + val recipeItem = item as RecipeListItem.RecipeItem + holder.bind(recipeItem) + holder.itemView.setOnClickListener { onRecipeClick(recipeItem.id) } + } + } + } + + override fun getItemCount(): Int = items.size + + class CategoryViewHolder(private val binding: VhRecipeCategoryBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: RecipeListItem.CategoryItem) { + binding.headerTitle.text = item.name + } + } + + class RecipeViewHolder(private val binding: VhRecipeItemBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: RecipeListItem.RecipeItem) { + binding.recipeTitle.text = item.title + binding.recipeDescription.text = item.description + binding.recipeImageThumbnail.load(item.imageUrl) { + transformations() + crossfade(true) + placeholder(R.drawable.ic_placeholder_shapes) + error(R.drawable.ic_placeholder_shapes) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeFragment.kt b/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeFragment.kt index e4460c1..97fb6a7 100644 --- a/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeFragment.kt +++ b/app/src/main/kotlin/ru/otus/cookbook/ui/RecipeFragment.kt @@ -9,54 +9,74 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewmodel.MutableCreationExtras +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import kotlinx.coroutines.launch import ru.otus.cookbook.data.Recipe import ru.otus.cookbook.databinding.FragmentRecipeBinding +import coil.load +import ru.otus.cookbook.R class RecipeFragment : Fragment() { - private val recipeId: Int get() = TODO("Use Safe Args to get the recipe ID: https://developer.android.com/guide/navigation/use-graph/pass-data#Safe-args") + private val args: RecipeFragmentArgs by navArgs() + private val recipeId: Int get() = args.recipeId private val binding = FragmentBindingDelegate(this) - private val model: RecipeFragmentViewModel by viewModels( - extrasProducer = { - MutableCreationExtras(defaultViewModelCreationExtras).apply { - set(RecipeFragmentViewModel.RECIPE_ID_KEY, recipeId) - } - }, - factoryProducer = { RecipeFragmentViewModel.Factory } - ) + private val model: RecipeFragmentViewModel by viewModels(extrasProducer = { + MutableCreationExtras(defaultViewModelCreationExtras).apply { + set(RecipeFragmentViewModel.RECIPE_ID_KEY, recipeId) + } + }, factoryProducer = { RecipeFragmentViewModel.Factory }) override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View = binding.bind( - container, - FragmentRecipeBinding::inflate - ) + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View = binding.bind(container, FragmentRecipeBinding::inflate) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { - model.recipe - .flowWithLifecycle(viewLifecycleOwner.lifecycle) - .collect(::displayRecipe) + model.recipe.flowWithLifecycle(viewLifecycleOwner.lifecycle).collect(::displayRecipe) } - } - /** - * Use to get recipe title and pass to confirmation dialog - */ - private fun getTitle(): String { - return model.recipe.value.title - } + val navBackStackEntry = findNavController().getBackStackEntry(R.id.recipeFragment) + navBackStackEntry.savedStateHandle.getLiveData("DELETE_CONFIRMED") + .observe(viewLifecycleOwner) { isConfirmed -> + if (isConfirmed == true) { + navBackStackEntry.savedStateHandle.remove("DELETE_CONFIRMED") + model.delete() + if (!findNavController().popBackStack(R.id.cookbookFragment, false)) { + findNavController().popBackStack() + } + } + } - private fun displayRecipe(recipe: Recipe) { - // Display the recipe + binding.withBinding { + btnDelete.setOnClickListener { + val action = + RecipeFragmentDirections.actionRecipeFragmentToDeleteRecipeDialogFragment( + getTitle() + ) + findNavController().navigate(action) + } + btnBack.setOnClickListener { + findNavController().navigateUp() + } + } } - private fun deleteRecipe() { - model.delete() + private fun displayRecipe(recipe: Recipe) = binding.withBinding { + toolbarRecipeTitle.text = recipe.title + recipeTitle.text = recipe.title + recipeSubhead.text = recipe.description + recipeDescription.text = recipe.steps.joinToString("\n\n") { "• $it" } + recipeImage.load(recipe.imageUrl) { + crossfade(true) + placeholder(R.drawable.ic_placeholder_cookbook) + error(R.drawable.ic_placeholder_cookbook) + } } + + private fun getTitle(): String = model.recipe.value.title } \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000..721ccc2 --- /dev/null +++ b/app/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000..4038baf --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000..9c973f7 --- /dev/null +++ b/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000..8d635ee --- /dev/null +++ b/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..96ad5b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..cdc19c7 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..d08741d --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_placeholder_cookbook.png b/app/src/main/res/drawable/ic_placeholder_cookbook.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd489d27a016e5f498d7c0da2248924dd5f0f51 GIT binary patch literal 9799 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 bS2v73`5c!U=uCmRFVOR6x@r|qti%5owDnn; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_placeholder_shapes.png b/app/src/main/res/drawable/ic_placeholder_shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf5f3d9508187188eaa6225283cec7dc5432ea6 GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBuD_>?V@L(#+u7{?pA7^Y)~CK#oF?)Af4O1+ z>mrAnv+JrR{^4Z3w9Ib3*Y_nWE==1}xv}=!&lg{+n-@ylD>)jF$TW3^gQ{9UCuc|! zCiCPPkyDp1Prh^S-l^Zecb_?b{`9e9ZEM!9^~@~sI(>|%-TCGB-$nKH|6gytdNuUs z?c0<6mMbp}h!Nknd-v~PIU75>Q(wPso#GP6^m3iKxw-QF<^-2%PKHb`cZG+CUwv#* zasT@D=~EmGnb?~f_w3uZt?K>v@854so7JVnQdd_O#-#r6;lqVpS9WSTDFjXrGvzEP zjY^!YCqMZOlc{d!g2OR}>XEFMO1CCU2L62f*tuw8sDgOE3vbEa)2F>px|%h*-0@^~ z7tYVmzZx*1S%*pThx2x+XKj<&773&XN-TPP?cuV5dskG%J+wKmmcCXnDAo4So3K&; zno-YZJ9~R?uSwiJd)sO&D<@8gaEtORYUJkT7MlF@X{slXz2td#SeOdiojDQD^OU^B z)pYHxZ|{+HJ|!plrZFckFYLGCA3Lq7_U7j5zkmOpG%KfCiAC7xw2IJ-sxxQKgiM+? zZCXlU;Ya_-GhZW01vEOI{fXUvd+n~bzkXFs+dJuz+0<7QH@quCJwm$K<&xmvE(J$0MI!i;gqze%^2qJrUB%tE@NUFZVL(TT_`rx$jI^ zwx(OS-9>#%ok9AOuL3N>9!*oalPU*8c8^3DhDTbS0uixqmH7vWoWp|Y$OPiGcF+t_qXDm%Ek0^w% z^PRL!RCqO)<7WHEwrrmk_EeX6=1R@g^;8k^@K7;$I#X;9i_-fqUqYOm!j4U?5Rf%x z{=Fn4YFoH?U+e|ps?)}5aZEEij%6vPthw!z>2=V$V}d4Mtm*kXA0IqeutMomVFmXS zC1>sw`{rx5zAKg)F6`dRsbuMWwdRAEM{NA%{mMU_KJ3`B<3wDk6=xjN%^RJelBf1e zT5@>H1JwyRUTdC8C%;~yd;ZdgU&~@1oIL5dwe3lQ>Y~_l8-FSA9#amUnR)PcnDRu8 zGmhJ>t*wRQPbh9X8FtyqTUx&Pi}S9MuKm~R{Df~_j=W_OeEXgJ>UXUyZTgNZ*LI8Q zE`8}OSo7-T?`?M{UG?wz7^rOEv7`9EfUkE_|G}%<3}k;sZa<^-_{WbOlTOU(Hxav; zd1OnYba6#RM6TA(SyH-NvU(4!*lHN_;OEbu{|(!^`KP{qd}q#d>4WF**wj6n*nGXB zZc56&(>uI27P{!`Eo$9a`*4b-9edE-hfkR9s~ + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 86a5d97..e990a3d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,13 +7,12 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_cookbook.xml b/app/src/main/res/layout/fragment_cookbook.xml index 77d9ef6..9d6365d 100644 --- a/app/src/main/res/layout/fragment_cookbook.xml +++ b/app/src/main/res/layout/fragment_cookbook.xml @@ -1,6 +1,64 @@ + android:layout_height="match_parent" + android:background="#FFFBFE"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recipe.xml b/app/src/main/res/layout/fragment_recipe.xml index 77d9ef6..34b0841 100644 --- a/app/src/main/res/layout/fragment_recipe.xml +++ b/app/src/main/res/layout/fragment_recipe.xml @@ -1,6 +1,125 @@ + android:layout_height="match_parent" + android:background="#FEF7FF"> + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/vh_recipe_category.xml b/app/src/main/res/layout/vh_recipe_category.xml index 006fd49..b0a07ea 100644 --- a/app/src/main/res/layout/vh_recipe_category.xml +++ b/app/src/main/res/layout/vh_recipe_category.xml @@ -1,6 +1,14 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/vh_recipe_item.xml b/app/src/main/res/layout/vh_recipe_item.xml index 006fd49..ff30950 100644 --- a/app/src/main/res/layout/vh_recipe_item.xml +++ b/app/src/main/res/layout/vh_recipe_item.xml @@ -1,6 +1,93 @@ - + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml new file mode 100644 index 0000000..399b215 --- /dev/null +++ b/app/src/main/res/navigation/navigation.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index d5b075a..69a9dc4 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,7 +1,3 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c8524cd..a6b3dae 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,5 +1,2 @@ - - #FF000000 - #FFFFFFFF - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8fce1d1..ad28f65 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ Cookbook + Удалить \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 47d6575..654ecf0 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -6,4 +6,9 @@ \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 515d8d1..752591f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.7.3" +coil = "2.7.0" kotlin = "2.1.0" coreKtx = "1.15.0" fragmentKtx = "1.8.5" @@ -18,6 +19,7 @@ datastore = "1.1.1" [libraries] +coil = { module = "io.coil-kt:coil", version.ref = "coil" } kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } diff --git a/webinar/src/main/res/values/colors.xml b/webinar/src/main/res/values/colors.xml index c8524cd..a6b3dae 100644 --- a/webinar/src/main/res/values/colors.xml +++ b/webinar/src/main/res/values/colors.xml @@ -1,5 +1,2 @@ - - #FF000000 - #FFFFFFFF - \ No newline at end of file + \ No newline at end of file diff --git a/webinar/src/main/res/values/strings.xml b/webinar/src/main/res/values/strings.xml index d1f6a33..f0b0647 100644 --- a/webinar/src/main/res/values/strings.xml +++ b/webinar/src/main/res/values/strings.xml @@ -4,8 +4,6 @@ Dashboard Notifications This is my home: %s - This is notifications Fragment - This is dashboard Fragment Login Password Login