diff --git a/.gitignore b/.gitignore index 3a2358d3..9721712b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,20 @@ +<<<<<<< HEAD +*/.gitignore +.gradle +.DS_Store + +======= +>>>>>>> ee3fc3113323afc9bd4b22252aab337033d65f47 # built application files *.apk *.ap_ +<<<<<<< HEAD +======= # Mac files .DS_Store +>>>>>>> ee3fc3113323afc9bd4b22252aab337033d65f47 # files for the dex VM *.dex @@ -13,6 +23,34 @@ # generated files bin/ +<<<<<<< HEAD +out/ +gen/ + +# Libraries used by the app +/libs + +# Build stuff (auto-generated by android update project ...) +build.xml +ant.properties +local.properties +project.properties + +# Eclipse project files +.classpath +.project + +# idea project files +.idea/ +.idea/.name +*.iml +*.ipr +*.iws + +# Gradle-based build +build/ + +======= gen/ # Ignore gradle files @@ -33,4 +71,5 @@ proguard-project.txt # Android Studio/IDEA *.iml -.idea \ No newline at end of file +.idea +>>>>>>> ee3fc3113323afc9bd4b22252aab337033d65f47 diff --git a/app/build.gradle b/app/build.gradle index 489ec986..206ae4c3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,16 +17,17 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + id 'androidx.navigation.safeargs.kotlin' } android { - compileSdkVersion 30 - buildToolsVersion "30.0.2" + compileSdkVersion 32 + buildToolsVersion "30.0.3" defaultConfig { applicationId "com.example.wordsapp" minSdkVersion 19 - targetSdkVersion 30 + targetSdkVersion 32 versionCode 1 versionName "1.0" @@ -59,4 +60,7 @@ dependencies { implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "com.google.android.material:material:$material_version" implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index de8ddd12..4588a9f2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,16 +24,13 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Words"> - - + - - + \ No newline at end of file diff --git a/app/src/main/java/com/example/wordsapp/DetailActivity.kt b/app/src/main/java/com/example/wordsapp/DetailActivity.kt deleted file mode 100644 index 54497552..00000000 --- a/app/src/main/java/com/example/wordsapp/DetailActivity.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.example.wordsapp - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.example.wordsapp.databinding.ActivityDetailBinding - - -class DetailActivity : AppCompatActivity() { - /** - * Provides global access to these variables from anywhere in the app - * via DetailActivity. without needing to create - * a DetailActivity instance. - */ - companion object { - const val LETTER = "letter" - const val SEARCH_PREFIX = "https://www.google.com/search?q=" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Retrieve a binding object that allows you to refer to views by id name - // Names are converted from snake case to camel case. - // For example, a View with the id word_one is referenced as binding.wordOne - val binding = ActivityDetailBinding.inflate(layoutInflater) - setContentView(binding.root) - - // Retrieve the LETTER from the Intent extras - // intent.extras.getString returns String? (String or null) - // so toString() guarantees that the value will be a String - val letterId = intent?.extras?.getString(LETTER).toString() - - val recyclerView = binding.recyclerView - recyclerView.layoutManager = LinearLayoutManager(this) - recyclerView.adapter = WordAdapter(letterId, this) - - // Adds a [DividerItemDecoration] between items - recyclerView.addItemDecoration( - DividerItemDecoration(this, DividerItemDecoration.VERTICAL) - ) - - title = getString(R.string.detail_prefix) + " " + letterId - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/wordsapp/LetterAdapter.kt b/app/src/main/java/com/example/wordsapp/LetterAdapter.kt index 9243ed04..bfc8c295 100644 --- a/app/src/main/java/com/example/wordsapp/LetterAdapter.kt +++ b/app/src/main/java/com/example/wordsapp/LetterAdapter.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.view.accessibility.AccessibilityNodeInfo import android.widget.Button import androidx.annotation.RequiresApi +import androidx.navigation.findNavController import androidx.recyclerview.widget.RecyclerView @@ -51,9 +52,9 @@ class LetterAdapter : */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LetterViewHolder { val layout = LayoutInflater - .from(parent.context) - .inflate(R.layout.item_view, parent, false) - + .from(parent.context) + .inflate(R.layout.item_view, parent, false) + // Setup custom accessibility delegate to set the text read layout.accessibilityDelegate = Accessibility return LetterViewHolder(layout) @@ -66,17 +67,10 @@ class LetterAdapter : val item = list.get(position) holder.button.text = item.toString() - // Assigns a [OnClickListener] to the button contained in the [ViewHolder] holder.button.setOnClickListener { - val context = holder.view.context - // Create an intent with a destination of DetailActivity - val intent = Intent(context, DetailActivity::class.java) - // Add the selected letter to the intent as extra data - // The text of Buttons are [CharSequence], a list of characters, - // so it must be explicitly converted into a [String]. - intent.putExtra(DetailActivity.LETTER, holder.button.text.toString()) - // Start an activity using the data and destination from the Intent. - context.startActivity(intent) + val action = LetterListFragmentDirections + .actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString()) + holder.view.findNavController().navigate(action) } } diff --git a/app/src/main/java/com/example/wordsapp/LetterListFragment.kt b/app/src/main/java/com/example/wordsapp/LetterListFragment.kt new file mode 100644 index 00000000..da1a388a --- /dev/null +++ b/app/src/main/java/com/example/wordsapp/LetterListFragment.kt @@ -0,0 +1,110 @@ +package com.example.wordsapp + +import android.os.Bundle +import android.view.* +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.wordsapp.databinding.FragmentLetterListBinding + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +private const val ARG_PARAM1 = "param1" +private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * Use the [LetterListFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class LetterListFragment : Fragment() { + private var _binding: FragmentLetterListBinding? = null + private val binding get() = _binding!! + private lateinit var recyclerView: RecyclerView + private var isLinearLayoutManager = true + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentLetterListBinding.inflate(inflater, container, false) + val view = binding.root + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + recyclerView = binding.recyclerView + chooseLayout() + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.layout_menu, menu) + + val layoutButton = menu.findItem(R.id.action_switch_layout) + setIcon(layoutButton) + } + + private fun chooseLayout() { + when (isLinearLayoutManager) { + true -> { + recyclerView.layoutManager = LinearLayoutManager(context) + recyclerView.adapter = LetterAdapter() + } + false -> { + recyclerView.layoutManager = GridLayoutManager(context, 4) + recyclerView.adapter = LetterAdapter() + } + } + } + + private fun setIcon(menuItem: MenuItem?) { + if (menuItem == null) + return + + // Set the drawable for the menu icon based on which LayoutManager is currently in use + + // An if-clause can be used on the right side of an assignment if all paths return a value. + // The following code is equivalent to + // if (isLinearLayoutManager) + // menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout) + // else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout) + menuItem.icon = + if (isLinearLayoutManager) + ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_grid_layout) + else ContextCompat.getDrawable(this.requireContext(), R.drawable.ic_linear_layout) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_switch_layout -> { + // Sets isLinearLayoutManager (a Boolean) to the opposite value + isLinearLayoutManager = !isLinearLayoutManager + // Sets layout and icon + chooseLayout() + setIcon(item) + + return true + } + // Otherwise, do nothing and use the core event handling + + // when clauses require that all possible paths be accounted for explicitly, + // for instance both the true and false cases if the value is a Boolean, + // or an else to catch all unhandled cases. + else -> super.onOptionsItemSelected(item) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wordsapp/MainActivity.kt b/app/src/main/java/com/example/wordsapp/MainActivity.kt index 50c924d2..b93904da 100644 --- a/app/src/main/java/com/example/wordsapp/MainActivity.kt +++ b/app/src/main/java/com/example/wordsapp/MainActivity.kt @@ -20,6 +20,9 @@ import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.setupActionBarWithNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -28,10 +31,10 @@ import com.example.wordsapp.databinding.ActivityMainBinding /** * Main Activity and entry point for the app. Displays a RecyclerView of letters. */ + class MainActivity : AppCompatActivity() { - private lateinit var recyclerView: RecyclerView - // Keeps track of which LayoutManager is in use for the [RecyclerView] - private var isLinearLayoutManager = true + + private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,73 +42,15 @@ class MainActivity : AppCompatActivity() { val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - recyclerView = binding.recyclerView - // Sets the LinearLayoutManager of the recyclerview - chooseLayout() - } - - /** - * Sets the LayoutManager for the [RecyclerView] based on the desired orientation of the list. - */ - private fun chooseLayout() { - if (isLinearLayoutManager) { - recyclerView.layoutManager = LinearLayoutManager(this) - } else { - recyclerView.layoutManager = GridLayoutManager(this, 4) - } - recyclerView.adapter = LetterAdapter() - } - - private fun setIcon(menuItem: MenuItem?) { - if (menuItem == null) - return + val navHostFragment = supportFragmentManager + .findFragmentById(R.id.nav_host_fragment) as NavHostFragment + navController = navHostFragment.navController - // Set the drawable for the menu icon based on which LayoutManager is currently in use + setupActionBarWithNavController(navController) - // An if-clause can be used on the right side of an assignment if all paths return a value. - // The following code is equivalent to - // if (isLinearLayoutManager) - // menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout) - // else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout) - menuItem.icon = - if (isLinearLayoutManager) - ContextCompat.getDrawable(this, R.drawable.ic_grid_layout) - else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout) } - /** - * Initializes the [Menu] to be used with the current [Activity] - */ - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.layout_menu, menu) - - val layoutButton = menu?.findItem(R.id.action_switch_layout) - // Calls code to set the icon based on the LinearLayoutManager of the RecyclerView - setIcon(layoutButton) - - return true - } - - /** - * Determines how to handle interactions with the selected [MenuItem] - */ - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_switch_layout -> { - // Sets isLinearLayoutManager (a Boolean) to the opposite value - isLinearLayoutManager = !isLinearLayoutManager - // Sets layout and icon - chooseLayout() - setIcon(item) - - return true - } - // Otherwise, do nothing and use the core event handling - - // when clauses require that all possible paths be accounted for explicitly, - // for instance both the true and false cases if the value is a Boolean, - // or an else to catch all unhandled cases. - else -> super.onOptionsItemSelected(item) - } + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp() || super.onSupportNavigateUp() } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wordsapp/WordAdapter.kt b/app/src/main/java/com/example/wordsapp/WordAdapter.kt index fc74f2cd..df2bd907 100644 --- a/app/src/main/java/com/example/wordsapp/WordAdapter.kt +++ b/app/src/main/java/com/example/wordsapp/WordAdapter.kt @@ -28,7 +28,7 @@ import androidx.annotation.RequiresApi import androidx.recyclerview.widget.RecyclerView /** - * Adapter for the [RecyclerView] in [DetailActivity]. + * Adapter for the [RecyclerView] in [WordListFragment]. */ class WordAdapter(private val letterId: String, context: Context) : RecyclerView.Adapter() { @@ -83,15 +83,13 @@ class WordAdapter(private val letterId: String, context: Context) : // Set the text of the WordViewHolder holder.button.text = item - - // Assigns a [OnClickListener] to the button contained in the [ViewHolder] holder.button.setOnClickListener { - val queryUrl: Uri = Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}") + val queryUrl: Uri = Uri.parse("${WordListFragment.SEARCH_PREFIX}${item}") val intent = Intent(Intent.ACTION_VIEW, queryUrl) context.startActivity(intent) } - } + } // Setup custom accessibility delegate to set the text read with // an accessibility service companion object Accessibility : View.AccessibilityDelegate() { diff --git a/app/src/main/java/com/example/wordsapp/WordListFragment.kt b/app/src/main/java/com/example/wordsapp/WordListFragment.kt new file mode 100644 index 00000000..8bf9319a --- /dev/null +++ b/app/src/main/java/com/example/wordsapp/WordListFragment.kt @@ -0,0 +1,65 @@ +package com.example.wordsapp + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.example.wordsapp.databinding.FragmentLetterListBinding + +// TODO: Rename parameter arguments, choose names that match +// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER +// private const val ARG_PARAM1 = "param1" +// private const val ARG_PARAM2 = "param2" + +/** + * A simple [Fragment] subclass. + * Use the [WordListFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class WordListFragment : Fragment() { + + companion object { + const val LETTER = "letter" + const val SEARCH_PREFIX = "https://www.google.com/search?q=" + } + + private var _binding: FragmentLetterListBinding? = null + private val binding get() = _binding!! + + private lateinit var letterId: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.let { + letterId = it.getString(LETTER).toString() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentLetterListBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val recyclerView = binding.recyclerView + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + recyclerView.adapter = WordAdapter(letterId, requireContext()) + + recyclerView.addItemDecoration( + DividerItemDecoration(context, DividerItemDecoration.VERTICAL) + ) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/res/drawable/ic_grid_layout.xml b/app/src/main/res/drawable/ic_grid_layout.xml index 60a42149..78298cd2 100644 --- a/app/src/main/res/drawable/ic_grid_layout.xml +++ b/app/src/main/res/drawable/ic_grid_layout.xml @@ -1,26 +1,9 @@ - - - + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_linear_layout.xml b/app/src/main/res/drawable/ic_linear_layout.xml index a01e25a9..9884aeb8 100644 --- a/app/src/main/res/drawable/ic_linear_layout.xml +++ b/app/src/main/res/drawable/ic_linear_layout.xml @@ -1,27 +1,10 @@ - - - - + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 884928d8..a462c3f7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -15,14 +15,17 @@ --> - + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_letter_list.xml b/app/src/main/res/layout/fragment_letter_list.xml new file mode 100644 index 00000000..74978bed --- /dev/null +++ b/app/src/main/res/layout/fragment_letter_list.xml @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/fragment_word_list.xml similarity index 96% rename from app/src/main/res/layout/activity_detail.xml rename to app/src/main/res/layout/fragment_word_list.xml index 93074c8e..6cf10f97 100644 --- a/app/src/main/res/layout/activity_detail.xml +++ b/app/src/main/res/layout/fragment_word_list.xml @@ -18,7 +18,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".DetailActivity"> + tools:context=".WordListFragment"> - + app:showAsAction="always"/> \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 00000000..a9fddcf5 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ 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 b3e4004c..bd95c23c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ - - Words + Words That Start With {letter} Words That Start With Switch Layout Look up word in a Browser Search Show Stored Words + + + Hello blank fragment + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c7ead94e..56d39017 100644 --- a/build.gradle +++ b/build.gradle @@ -16,19 +16,23 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { - appcompat_version = "1.2.0" - constraintlayout_version = "2.0.2" - core_ktx_version = "1.3.2" - kotlin_version = "1.3.72" - material_version = "1.2.1" + + appcompat_version = "1.4.1" + constraintlayout_version = "2.1.3" + core_ktx_version = "1.7.0" + kotlin_version = "1.6.21" + material_version = "1.5.0" + nav_version = '2.4.2' + } repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4f04f629..c3af661f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip