├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ └── Feature_request.md ├── pull_request_template.md └── workflows │ └── android.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── RELEASE_NOTES.md ├── art └── newshowcase.png ├── bintrayconfig.gradle ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── afollestad │ │ └── materialcab │ │ ├── Callbacks.kt │ │ ├── MaterialCab.kt │ │ ├── attached │ │ ├── AttachedCab.kt │ │ └── RealAttachedCab.kt │ │ └── internal │ │ └── Extensions.kt │ ├── res-public │ └── values │ │ └── public.xml │ └── res │ ├── drawable │ └── mcab_nav_close.xml │ ├── layout │ └── mcab_toolbar.xml │ └── values │ └── dimens.xml ├── sample.apk ├── sample ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── afollestad │ │ └── materialcabsample │ │ ├── Items.kt │ │ ├── MainActivity.kt │ │ ├── Util.kt │ │ └── ViewHolders.kt │ └── res │ ├── drawable-v21 │ └── list_ripple_selector.xml │ ├── drawable │ ├── ic_action_copy.xml │ ├── ic_action_cut.xml │ ├── ic_action_paste.xml │ ├── ic_action_selectall.xml │ ├── ic_action_share.xml │ └── list_selector.xml │ ├── layout │ ├── activity_main.xml │ └── listitem_main.xml │ ├── menu │ └── menu_cab.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-v21 │ └── styles.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── settings.gradle ├── spotless.gradle ├── spotless.license.kt └── versionsPlugin.gradle /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: afollestad 2 | ko_fi: afollestad 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is crashing or not working as intended 4 | labels: bug 5 | 6 | --- 7 | 8 | *Please consider making a Pull Request if you are capable of doing so.* 9 | 10 | **Library Version:** 11 | 12 | 1.3.x 13 | 14 | **Affected Device(s):** 15 | 16 | Google Pixel 3 XL with Android 9.0 17 | 18 | **Describe the Bug:** 19 | 20 | A clear description of what is the bug is. 21 | 22 | **To Reproduce:** 23 | 1. 24 | 2. 25 | 3. 26 | 27 | **Expected Behavior:** 28 | 29 | A clear description of what you expected to happen. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | labels: enhancement 5 | 6 | --- 7 | 8 | *Please consider making a Pull Request if you are capable of doing so.* 9 | 10 | **What module does this apply to?** 11 | 12 | Core? Input? Files? Color? 13 | 14 | **Description what you'd like to happen:** 15 | 16 | A clear description if the feature or behavior you'd like implemented. 17 | 18 | **Describe alternatives you've considered:** 19 | 20 | A clear description of any alternative solutions you've considered. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Guidelines 2 | 3 | 1. You must run the `spotlessApply` task before commiting, either through Android Studio or with `./gradlew spotlessApply`. 4 | 2. A PR should be focused and contained. If you are changing multiple unrelated things, they should be in separate PRs. 5 | 3. A PR should fix a bug or solve a problem - something that only you would use is not necessarily something that should be published. 6 | 4. Give your PR a detailed title and description - look over your code one last time before actually creating the PR. Give it a self-review. 7 | 8 | **If you do not follow the guidelines, your PR will be rejected.** 9 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Gradle 17 | run: ./gradlew build check 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Android ### 4 | # Built application files 5 | *.ap_ 6 | 7 | # Files for the Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | /*/build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | ### Android Patch ### 32 | gen-external-apklibs 33 | 34 | 35 | ### Intellij ### 36 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 37 | 38 | *.iml 39 | 40 | ## Directory-based project format: 41 | .idea/ 42 | # if you remove the above rule, at least ignore the following: 43 | 44 | # User-specific stuff: 45 | # .idea/workspace.xml 46 | # .idea/tasks.xml 47 | # .idea/dictionaries 48 | 49 | # Sensitive or high-churn files: 50 | # .idea/dataSources.ids 51 | # .idea/dataSources.xml 52 | # .idea/sqlDataSources.xml 53 | # .idea/dynamic.xml 54 | # .idea/uiDesigner.xml 55 | 56 | # Gradle: 57 | # .idea/gradle.xml 58 | # .idea/libraries 59 | 60 | # Mongo Explorer plugin: 61 | # .idea/mongoSettings.xml 62 | 63 | ## File-based project format: 64 | *.ipr 65 | *.iws 66 | 67 | ## Plugin-specific files: 68 | 69 | # IntelliJ 70 | /out/ 71 | 72 | # mpeltonen/sbt-idea plugin 73 | .idea_modules/ 74 | 75 | # JIRA plugin 76 | atlassian-ide-plugin.xml 77 | 78 | # Crashlytics plugin (for Android Studio and IntelliJ) 79 | com_crashlytics_export_strings.xml 80 | crashlytics.properties 81 | crashlytics-build.properties 82 | 83 | 84 | ### OSX ### 85 | .DS_Store 86 | .AppleDouble 87 | .LSOverride 88 | 89 | # Icon must end with two \r 90 | Icon 91 | 92 | 93 | # Thumbnails 94 | ._* 95 | 96 | # Files that might appear in the root of a volume 97 | .DocumentRevisions-V100 98 | .fseventsd 99 | .Spotlight-V100 100 | .TemporaryItems 101 | .Trashes 102 | .VolumeIcon.icns 103 | 104 | # Directories potentially created on remote AFP share 105 | .AppleDB 106 | .AppleDesktop 107 | Network Trash Folder 108 | Temporary Items 109 | .apdisk 110 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ###### Apache License, Version 2.0 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ 2 | 3 | ### TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 5 | ###### 1. Definitions. 6 | 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 12 | 13 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 14 | 15 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 16 | 17 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 18 | 19 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 20 | 21 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 22 | 23 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 24 | 25 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 26 | 27 | ###### 2. Grant of Copyright License. 28 | 29 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | ###### 3. Grant of Patent License. 32 | 33 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | ###### 4. Redistribution. 36 | 37 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 38 | 39 | You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 40 | 41 | ###### 5. Submission of Contributions. 42 | 43 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. 46 | 47 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | ###### 7. Disclaimer of Warranty. 50 | 51 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 52 | 53 | ###### 8. Limitation of Liability. 54 | 55 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 56 | 57 | ###### 9. Accepting Warranty or Additional Liability. 58 | 59 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 60 | 61 | ### END OF TERMS AND CONDITIONS 62 | 63 | ### APPENDIX: How to apply the Apache License to your work 64 | 65 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 66 | 67 | ### Copyright 2016 Aidan Follestad 68 | 69 | 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 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | 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. 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material Contextual Action Bar 2 | 3 | [ ![Maven Central](https://img.shields.io/maven-central/v/com.afollestad/material-cab?style=flat&label=Maven+Central) ](https://repo1.maven.org/maven2/com/afollestad/material-cab) 4 | [![Android CI](https://github.com/afollestad/material-cab/workflows/Android%20CI/badge.svg)](https://github.com/afollestad/material-cab/actions?query=workflow%3A%22Android+CI%22) 5 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html) 6 | 7 | 8 | 9 | Material CAB allows you to implement a customizable and flexible contextual action bar in your app. 10 | The traditional stock CAB on Android is limited to being placed at the top of your Activity, 11 | and the navigation drawer cannot go over it. This library lets you choose its exact location, 12 | and a toolbar is used, allowing views to be be placed over and under it. 13 | 14 | ## Table of Contents 15 | 16 | 1. [Gradle Dependency](#gradle-dependency) 17 | 2. [Getting Started](#getting-started) 18 | 3. [Destroying the CAB](#destroying-the-cab) 19 | 20 | --- 21 | 22 | ## Gradle Dependency 23 | 24 | [ ![Maven Central](https://img.shields.io/maven-central/v/com.afollestad/material-cab?style=flat&label=Maven+Central) ](https://repo1.maven.org/maven2/com/afollestad/material-cab) 25 | 26 | Add Material CAB to your module's `build.gradle` dependencies block: 27 | 28 | ```Gradle 29 | dependencies { 30 | 31 | implementation 'com.afollestad:material-cab:2.0.1' 32 | } 33 | ``` 34 | 35 | --- 36 | 37 | ## Getting Started 38 | 39 | This library attaches to your `Activity` by taking the place of a `ViewStub` in your Activity layout. 40 | For an example, this is similar to the main layout of the sample project: 41 | 42 | ```xml 43 | 48 | 49 | 52 | 53 | 56 | 57 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | ``` 70 | 71 | You create/attach a Material CAB in an Activity like this: 72 | 73 | ```kotlin 74 | class MyActivity : AppCompatActivity() { 75 | private var cab: AttachedCab? = null 76 | 77 | override fun onCreate(savedInstanceState: Bundle?) { 78 | super.onCreate(savedInstanceState) 79 | 80 | // createCab is an extension on Activity/Fragment 81 | cab = createCab(R.id.cab_stub) { 82 | title(R.string.some_title) 83 | menu(R.menu.some_menu) 84 | slideDown() 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | `R.id.cab_stub` references the `ViewStub`, which is replaced with the CAB toolbar. 91 | 92 | In addition, you can also pass the ID of a `ViewGroup` (such as a `FrameLayout`). The CAB will 93 | get appended as a child to that view group. 94 | 95 | --- 96 | 97 | ## Configuration 98 | 99 | You can configure various properties about your CAB during attachment: 100 | 101 | ```kotlin 102 | val attachedCab = createCab(R.id.cab_stub) { 103 | title(R.string.some_title) 104 | title(literal = "Some Title") 105 | subtitle(R.string.some_subtitle) 106 | subtitle(literal = "Some Subtitle") 107 | 108 | titleColor(R.color.white) 109 | titleColor(literal = Color.WHITE) 110 | subtitleColor(R.color.white) 111 | subtitleColor(literal = Color.WHITE) 112 | 113 | popupTheme(R.style.ThemeOverlay_AppCompat_Light) 114 | 115 | contentInsetStart(R.dimen.mcab_default_content_inset) 116 | contentInsetStart(literal = 52) 117 | 118 | backgroundColor(R.color.dark_gray) 119 | backgroundColor(literal = Color.DARK_GRAY) 120 | 121 | closeDrawable(R.drawable.close_icon) 122 | 123 | menu(R.menu.cab_menu_items) 124 | 125 | onCreate { cab, menu -> 126 | ... 127 | } 128 | onSelection { item -> 129 | ... 130 | true // allow selection? 131 | } 132 | onDestroy { cab -> 133 | ... 134 | true // allow destruction? 135 | } 136 | 137 | animateOnCreate { view, animator -> 138 | // Animate the view with its animator. 139 | // See the source of fadeIn(Long) or slideDown(Long) for an example. 140 | } 141 | 142 | animateOnDestroy { view, animator -> 143 | // Animate the view with its animator. 144 | // See the source of fadeIn(Long) or slideDown(Long) for an example. 145 | } 146 | 147 | // Sets animateOnCreate and animateOnDestroy to fade the CAB. Duration is optional, 250 is default. 148 | fadeIn(durationMs = 250) 149 | 150 | // Sets animateOnCreate and animateOnDestroy to slide the CAB up/down. Duration is optional, 250 is default. 151 | slideDown(durationMs = 250) 152 | } 153 | ``` 154 | 155 | --- 156 | 157 | ## Destroying the CAB 158 | 159 | The navigation icon in your CAB toolbar (far left button) will trigger this method, but you 160 | can manually call it whenever you'd like as well: 161 | 162 | 163 | ```kotlin 164 | val cab: AttachedCab? = // ... 165 | 166 | val isDestroyed = cab.isDestroyed() // true if null or destroyed 167 | val isActive = cab.isActive() // true if not destroyed 168 | 169 | cab.destroy() 170 | ``` 171 | 172 | This will invoke the onDestroy callback. If the callback returns true, the CAB is destroyed. 173 | If the CAB replaced a ViewStub, it's hidden (`GONE`), otherwise it's removed from the layout. 174 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | 2.0.1 2 | 3 | Allow the `createCab` extension to be called from a Fragment as well. -------------------------------------------------------------------------------- /art/newshowcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/art/newshowcase.png -------------------------------------------------------------------------------- /bintrayconfig.gradle: -------------------------------------------------------------------------------- 1 | if (!project.rootProject.file('local.properties').exists()) { 2 | println "Not applying bintrayconfig.gradle" 3 | return 4 | } 5 | apply plugin: 'com.novoda.bintray-release' 6 | 7 | def getBintrayUserAndKey() { 8 | Properties properties = new Properties() 9 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 10 | return [ 11 | properties.getProperty("bintray.user"), 12 | properties.getProperty("bintray.apikey") 13 | ] 14 | } 15 | 16 | if (versions == null || versions.publishVersion == null) { 17 | throw new IllegalStateException("Unable to reference publishVersion") 18 | } 19 | 20 | task checkBintrayConfig { 21 | doLast { 22 | def (user, key) = getBintrayUserAndKey() 23 | if (user == null || user.isEmpty() || 24 | key == null || key.isEmpty()) { 25 | throw new IllegalStateException("Must specify Bintray user/API key in your local.properties.") 26 | } 27 | } 28 | } 29 | 30 | afterEvaluate { 31 | bintrayUpload.dependsOn checkBintrayConfig 32 | } 33 | 34 | def (user, key) = getBintrayUserAndKey() 35 | publish { 36 | bintrayUser = user 37 | bintrayKey = key 38 | userOrg = 'drummer-aidan' 39 | groupId = 'com.afollestad' 40 | artifactId = 'material-cab' 41 | publishVersion = versions.publishVersion 42 | desc = 43 | 'A simple library for Android, which replaces the stock contextual action bar to allow more customization.' 44 | website = 'https://github.com/afollestad/material-cab' 45 | dryRun = false 46 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: './dependencies.gradle' 2 | apply from: './versionsPlugin.gradle' 3 | 4 | buildscript { 5 | apply from: './dependencies.gradle' 6 | 7 | repositories { 8 | google() 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:' + versions.gradlePlugin 14 | classpath 'com.diffplug.spotless:spotless-plugin-gradle:' + versions.spotlessPlugin 15 | classpath 'com.novoda:bintray-release:' + versions.bintrayRelease 16 | classpath 'com.github.ben-manes:gradle-versions-plugin:' + versions.versionsPlugin 17 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:' + versions.kotlin 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | } 26 | 27 | tasks.withType(Javadoc).all { 28 | enabled = false 29 | } 30 | } -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext.versions = [ 2 | minSdk : 19, 3 | compileSdk : 29, 4 | buildTools : '29.0.0', 5 | publishVersion : '2.0.1', 6 | publishVersionCode: 13, 7 | 8 | gradlePlugin : '3.4.2', 9 | kotlin : '1.3.41', 10 | spotlessPlugin : '3.23.1', 11 | versionsPlugin : '0.21.0', 12 | bintrayRelease : '0.9.1', 13 | 14 | androidx : '1.0.2', 15 | androidxAnnotation: '1.1.0', 16 | recyclerView : '1.0.0', 17 | recyclical : '1.0.0' 18 | ] 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply from: '../dependencies.gradle' 4 | 5 | apply from: '../bintrayconfig.gradle' 6 | 7 | android { 8 | compileSdkVersion versions.compileSdk 9 | buildToolsVersion versions.buildTools 10 | 11 | defaultConfig { 12 | minSdkVersion versions.minSdk 13 | targetSdkVersion versions.compileSdk 14 | versionCode versions.publishVersionCode 15 | versionName versions.publishVersion 16 | } 17 | 18 | sourceSets { 19 | main.res.srcDirs = [ 20 | 'src/main/res', 21 | 'src/main/res-public' 22 | ] 23 | } 24 | 25 | compileOptions { 26 | kotlinOptions.freeCompilerArgs += ['-module-name', "com.afollestad.material-cab"] 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation 'androidx.appcompat:appcompat:' + versions.androidx 32 | implementation 'androidx.annotation:annotation:' + versions.androidxAnnotation 33 | 34 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:' + versions.kotlin 35 | } 36 | 37 | apply from: '../spotless.gradle' 38 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/src/main/java/com/afollestad/materialcab/Callbacks.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcab 17 | 18 | import android.view.Menu 19 | import android.view.MenuItem 20 | import android.view.View 21 | import android.view.ViewPropertyAnimator 22 | import com.afollestad.materialcab.attached.AttachedCab 23 | 24 | typealias CreateCallback = (cab: AttachedCab, menu: Menu) -> Unit 25 | 26 | typealias SelectCallback = (item: MenuItem) -> Boolean 27 | 28 | typealias DestroyCallback = (cab: AttachedCab) -> Boolean 29 | 30 | typealias CabAnimator = (view: View, animator: ViewPropertyAnimator) -> Unit 31 | 32 | typealias CabApply = AttachedCab.() -> Unit 33 | 34 | internal fun List.invokeAll( 35 | cab: AttachedCab, 36 | menu: Menu 37 | ) = forEach { it(cab, menu) } 38 | 39 | internal fun List.invokeAll(menuItem: MenuItem): Boolean { 40 | if (isEmpty()) { 41 | return false 42 | } 43 | return all { it(menuItem) } 44 | } 45 | 46 | internal fun List.invokeAll(cab: AttachedCab): Boolean { 47 | if (isEmpty()) { 48 | return true 49 | } 50 | return all { it(cab) } 51 | } 52 | -------------------------------------------------------------------------------- /library/src/main/java/com/afollestad/materialcab/MaterialCab.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 17 | 18 | package com.afollestad.materialcab 19 | 20 | import android.app.Activity 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import android.view.ViewStub 24 | import androidx.annotation.IdRes 25 | import androidx.appcompat.widget.Toolbar 26 | import androidx.fragment.app.Fragment 27 | import com.afollestad.materialcab.attached.AttachedCab 28 | import com.afollestad.materialcab.attached.RealAttachedCab 29 | import com.afollestad.materialcab.internal.idName 30 | import com.afollestad.materialcab.internal.inflate 31 | 32 | /** 33 | * Creates a new contextual action bar. Attaches to a view stub or into a view group 34 | * by ID [attachToId], inside the receiving [Activity]. 35 | */ 36 | fun Activity.createCab( 37 | @IdRes attachToId: Int, 38 | exec: CabApply 39 | ): AttachedCab { 40 | val attachToName = idName(attachToId) 41 | val attachToView = findViewById(attachToId) 42 | var replacedViewStub = true 43 | 44 | val toolbar: Toolbar = when (attachToView) { 45 | is Toolbar -> { 46 | // This is most likely a previous destroyed CAB that 47 | // was inflated into a ViewStub. 48 | attachToView 49 | } 50 | is ViewStub -> { 51 | // We assign an ID so that we can find it again later 52 | // when re-attaching, since destroying won't remove this, 53 | // only hide it. 54 | attachToView.inflatedId = attachToId 55 | attachToView.layoutResource = R.layout.mcab_toolbar 56 | attachToView.inflate() as Toolbar 57 | } 58 | is ViewGroup -> { 59 | replacedViewStub = false 60 | attachToView.inflate(R.layout.mcab_toolbar) 61 | .also { 62 | attachToView.addView(it) 63 | } 64 | } 65 | else -> throw IllegalStateException( 66 | "Unable to attach to $attachToName, it's not a ViewStub or ViewGroup." 67 | ) 68 | } 69 | 70 | return RealAttachedCab( 71 | context = this, 72 | toolbar = toolbar, 73 | replacedViewStub = replacedViewStub 74 | ).apply { 75 | exec() 76 | show() 77 | } 78 | } 79 | 80 | /** 81 | * Calls [createCab] on the Fragment's Activity. 82 | */ 83 | fun Fragment.createCab( 84 | @IdRes attachToId: Int, 85 | exec: CabApply 86 | ): AttachedCab { 87 | val context = activity ?: throw IllegalStateException( 88 | "Fragment ${this::class.java.name} is not attached to an Activity." 89 | ) 90 | return context.createCab(attachToId, exec) 91 | } 92 | -------------------------------------------------------------------------------- /library/src/main/java/com/afollestad/materialcab/attached/AttachedCab.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcab.attached 17 | 18 | import android.view.Menu 19 | import androidx.annotation.ColorInt 20 | import androidx.annotation.ColorRes 21 | import androidx.annotation.DimenRes 22 | import androidx.annotation.DrawableRes 23 | import androidx.annotation.MenuRes 24 | import androidx.annotation.Px 25 | import androidx.annotation.StringRes 26 | import androidx.annotation.StyleRes 27 | import com.afollestad.materialcab.CabAnimator 28 | import com.afollestad.materialcab.CreateCallback 29 | import com.afollestad.materialcab.DestroyCallback 30 | import com.afollestad.materialcab.SelectCallback 31 | 32 | /** 33 | * A handle to a Contextual Action Bar instance. 34 | * 35 | * @author Aidan Follestad (@afollestad) 36 | */ 37 | interface AttachedCab { 38 | /** Sets the CAB's title. */ 39 | fun title( 40 | @StringRes res: Int? = null, 41 | literal: String? = null 42 | ) 43 | 44 | /** Sets the CAB's subtitle. */ 45 | fun subtitle( 46 | @StringRes res: Int? = null, 47 | literal: String? = null 48 | ) 49 | 50 | /** Sets the CAB's title text color. */ 51 | fun titleColor( 52 | @ColorRes res: Int? = null, 53 | @ColorInt literal: Int? = null 54 | ) 55 | 56 | /** Sets the CAB's subtitle text color. */ 57 | fun subtitleColor( 58 | @ColorRes res: Int? = null, 59 | @ColorInt literal: Int? = null 60 | ) 61 | 62 | /** Sets the CAB's popup (overflow) menu theme. */ 63 | fun popupTheme(@StyleRes theme: Int) 64 | 65 | /** Sets the CAB's content inset (the start padding). */ 66 | fun contentInsetStart( 67 | @DimenRes res: Int? = null, 68 | @Px literal: Int? 69 | ) 70 | 71 | /** Sets the CAB's background color. */ 72 | fun backgroundColor( 73 | @ColorRes res: Int? = null, 74 | @ColorInt literal: Int? = null 75 | ) 76 | 77 | /** Sets the CAB's close (exit) drawable. */ 78 | fun closeDrawable(@DrawableRes res: Int) 79 | 80 | /** Sets the CAB's menu. */ 81 | fun menu(@MenuRes res: Int) 82 | 83 | /** Gets the CAB's menu. */ 84 | fun getMenu(): Menu 85 | 86 | /** Sets a callback invoked when the CAB is being created/shown. */ 87 | fun onCreate(callback: CreateCallback) 88 | 89 | /** Sets a callback invoked when a CAB menu item is selected. */ 90 | fun onSelection(callback: SelectCallback) 91 | 92 | /** Sets a callback invoked when the CAB is being destroyed. */ 93 | fun onDestroy(callback: DestroyCallback) 94 | 95 | /** Creates a custom animator for the CAB when it's creating/showing itself. */ 96 | fun animateOnCreate(animator: CabAnimator) 97 | 98 | /** Creates a custom animator for the CAB when it's destroying/hiding itself. */ 99 | fun animateOnDestroy(animator: CabAnimator) 100 | 101 | /** A shortcut around [animateOnCreate] and [animateOnDestroy] that fades in the CAB in. */ 102 | fun fadeIn(durationMs: Long = 250) 103 | 104 | /** A shortcut around [animateOnCreate] and [animateOnDestroy] that slides the CAB in */ 105 | fun slideDown(durationMs: Long = 200) 106 | } 107 | 108 | /** Returns true if the CAB is destroyed and unusable, or if the receiver is null. */ 109 | fun AttachedCab?.isDestroyed(): Boolean { 110 | return (this as? RealAttachedCab)?.isDestroyed() ?: true 111 | } 112 | 113 | /** Returns true if the CAB is active and usable. */ 114 | fun AttachedCab?.isActive(): Boolean = !isDestroyed() 115 | 116 | /** 117 | * Destroys the contextual action bar, freeing up resources and references. Also makes this 118 | * CAB instance unusable. Returns true if the receiver was not null and the CAB was destroyed. 119 | */ 120 | fun AttachedCab?.destroy(): Boolean { 121 | return (this as? RealAttachedCab)?.startDestroy() ?: false 122 | } 123 | -------------------------------------------------------------------------------- /library/src/main/java/com/afollestad/materialcab/attached/RealAttachedCab.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcab.attached 17 | 18 | import android.app.Activity 19 | import android.graphics.Color 20 | import android.graphics.drawable.Drawable 21 | import android.view.Menu 22 | import android.view.View.GONE 23 | import android.view.View.VISIBLE 24 | import androidx.annotation.ColorInt 25 | import androidx.annotation.ColorRes 26 | import androidx.annotation.DimenRes 27 | import androidx.annotation.MenuRes 28 | import androidx.annotation.Px 29 | import androidx.annotation.StringRes 30 | import androidx.annotation.StyleRes 31 | import androidx.appcompat.widget.Toolbar 32 | import com.afollestad.materialcab.CabAnimator 33 | import com.afollestad.materialcab.CreateCallback 34 | import com.afollestad.materialcab.DestroyCallback 35 | import com.afollestad.materialcab.R 36 | import com.afollestad.materialcab.SelectCallback 37 | import com.afollestad.materialcab.internal.colorAttr 38 | import com.afollestad.materialcab.internal.drawable 39 | import com.afollestad.materialcab.internal.onAnimationEnd 40 | import com.afollestad.materialcab.internal.onLayout 41 | import com.afollestad.materialcab.internal.removeSelf 42 | import com.afollestad.materialcab.internal.requireOneColor 43 | import com.afollestad.materialcab.internal.requireOneDimen 44 | import com.afollestad.materialcab.internal.requireOneString 45 | import com.afollestad.materialcab.internal.tint 46 | import com.afollestad.materialcab.invokeAll 47 | 48 | /** @author Aidan Follestad (@afollestad) */ 49 | class RealAttachedCab internal constructor( 50 | private var context: Activity?, 51 | private var toolbar: Toolbar?, 52 | private val replacedViewStub: Boolean 53 | ) : AttachedCab { 54 | init { 55 | titleColor(literal = Color.WHITE) 56 | backgroundColor(literal = attachedContext.colorAttr(R.attr.colorPrimaryDark, Color.GRAY)) 57 | } 58 | 59 | private var isDestroying: Boolean = false 60 | private val attachedContext: Activity 61 | get() = context ?: throw IllegalStateException("Contextual action bar is already destroyed.") 62 | private val attachedToolbar: Toolbar 63 | get() = toolbar ?: throw IllegalStateException("Contextual action bar is already destroyed.") 64 | 65 | private var titleTextColor: Int = Color.WHITE 66 | private var closeDrawable: Drawable = attachedContext.drawable(R.drawable.mcab_nav_close) 67 | 68 | private var createCallbacks = mutableListOf() 69 | private var selectCallbacks = mutableListOf() 70 | private var destroyCallbacks = mutableListOf() 71 | 72 | private var createAnimator: CabAnimator? = null 73 | private var destroyAnimator: CabAnimator? = null 74 | 75 | internal fun show() = attachedToolbar.run { 76 | isDestroying = false 77 | translationY = 0f 78 | alpha = 1f 79 | 80 | navigationIcon = closeDrawable.tint(titleTextColor) 81 | setNavigationOnClickListener { destroy() } 82 | createCallbacks.invokeAll(this@RealAttachedCab, menu) 83 | animate() 84 | .setListener(null) 85 | .cancel() 86 | 87 | visibility = VISIBLE 88 | bringToFront() 89 | onLayout { createAnimator?.invoke(this, animate()) } 90 | } 91 | 92 | override fun title( 93 | @StringRes res: Int?, 94 | literal: String? 95 | ) { 96 | attachedToolbar.title = attachedContext.requireOneString(literal, res) 97 | } 98 | 99 | override fun subtitle( 100 | @StringRes res: Int?, 101 | literal: String? 102 | ) { 103 | attachedToolbar.subtitle = attachedContext.requireOneString(literal, res) 104 | } 105 | 106 | override fun titleColor( 107 | @ColorRes res: Int?, 108 | @ColorInt literal: Int? 109 | ) { 110 | titleTextColor = attachedContext.requireOneColor(literal, res) 111 | attachedToolbar.setTitleTextColor(titleTextColor) 112 | } 113 | 114 | override fun subtitleColor( 115 | @ColorRes res: Int?, 116 | @ColorInt literal: Int? 117 | ) { 118 | attachedToolbar.setSubtitleTextColor(attachedContext.requireOneColor(literal, res)) 119 | } 120 | 121 | override fun popupTheme(@StyleRes theme: Int) { 122 | attachedToolbar.popupTheme = theme 123 | } 124 | 125 | override fun contentInsetStart( 126 | @DimenRes res: Int?, 127 | @Px literal: Int? 128 | ) { 129 | attachedToolbar.setContentInsetsRelative( 130 | attachedContext.requireOneDimen(literal, res), 131 | 0 132 | ) 133 | } 134 | 135 | override fun backgroundColor( 136 | @ColorRes res: Int?, 137 | @ColorInt literal: Int? 138 | ) { 139 | attachedToolbar.setBackgroundColor(attachedContext.requireOneColor(literal, res)) 140 | } 141 | 142 | override fun closeDrawable(@DimenRes res: Int) { 143 | closeDrawable = attachedContext.drawable(res) 144 | attachedToolbar.navigationIcon = closeDrawable.tint(titleTextColor) 145 | } 146 | 147 | override fun menu(@MenuRes res: Int) { 148 | attachedToolbar.run { 149 | menu?.clear() 150 | if (res != 0) { 151 | inflateMenu(res) 152 | setOnMenuItemClickListener(menuClickListener) 153 | } else { 154 | setOnMenuItemClickListener(null) 155 | } 156 | } 157 | } 158 | 159 | override fun getMenu(): Menu = attachedToolbar.menu 160 | 161 | override fun onCreate(callback: CreateCallback) { 162 | this.createCallbacks.add(callback) 163 | } 164 | 165 | override fun onSelection(callback: SelectCallback) { 166 | this.selectCallbacks.add(callback) 167 | } 168 | 169 | override fun onDestroy(callback: DestroyCallback) { 170 | this.destroyCallbacks.add(callback) 171 | } 172 | 173 | override fun animateOnCreate(animator: CabAnimator) { 174 | this.createAnimator = animator 175 | } 176 | 177 | override fun animateOnDestroy(animator: CabAnimator) { 178 | this.destroyAnimator = animator 179 | } 180 | 181 | override fun fadeIn(durationMs: Long) { 182 | animateOnCreate { view, animator -> 183 | view.alpha = 0f 184 | animator.alpha(1f) 185 | .setDuration(durationMs) 186 | .start() 187 | } 188 | animateOnDestroy { view, animator -> 189 | view.alpha = 1f 190 | animator.alpha(0f) 191 | .setDuration(durationMs) 192 | .start() 193 | } 194 | } 195 | 196 | override fun slideDown(durationMs: Long) { 197 | animateOnCreate { view, animator -> 198 | view.translationY = (-view.measuredHeight).toFloat() 199 | animator.translationY(0f) 200 | .setDuration(durationMs) 201 | .start() 202 | } 203 | animateOnDestroy { view, animator -> 204 | view.translationY = 0f 205 | val endTranslation = (-view.measuredHeight).toFloat() 206 | animator.translationY(endTranslation) 207 | .setDuration(durationMs) 208 | .start() 209 | } 210 | } 211 | 212 | fun isDestroyed(): Boolean { 213 | return context == null || toolbar == null || isDestroying 214 | } 215 | 216 | fun startDestroy(): Boolean = synchronized(isDestroying) { 217 | if (isDestroyed()) return false 218 | isDestroying = true 219 | 220 | val canDestroy = destroyCallbacks.invokeAll(this@RealAttachedCab) 221 | if (!canDestroy) { 222 | isDestroying = false 223 | return false 224 | } 225 | 226 | val animator = destroyAnimator 227 | attachedToolbar.run { 228 | if (animator != null) { 229 | animate().cancel() 230 | animate().onAnimationEnd { finalizeDestroy() } 231 | animator(this, animate()) 232 | } else { 233 | finalizeDestroy() 234 | } 235 | } 236 | 237 | return true 238 | } 239 | 240 | private fun finalizeDestroy() { 241 | attachedToolbar.visibility = GONE 242 | if (!replacedViewStub) { 243 | // If we replaced a view stub, we should not remove the toolbar because 244 | // we end up with nothing to attach to or replace later. 245 | attachedToolbar.removeSelf() 246 | } 247 | toolbar = null 248 | context = null 249 | } 250 | 251 | private val menuClickListener = Toolbar.OnMenuItemClickListener { item -> 252 | selectCallbacks.invokeAll(item) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /library/src/main/java/com/afollestad/materialcab/internal/Extensions.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcab.internal 17 | 18 | import android.animation.AnimatorListenerAdapter 19 | import android.content.Context 20 | import android.graphics.drawable.Drawable 21 | import android.view.LayoutInflater 22 | import android.view.View 23 | import android.view.ViewGroup 24 | import android.view.ViewPropertyAnimator 25 | import android.view.ViewTreeObserver 26 | import androidx.annotation.AttrRes 27 | import androidx.annotation.ColorInt 28 | import androidx.annotation.ColorRes 29 | import androidx.annotation.DimenRes 30 | import androidx.annotation.DrawableRes 31 | import androidx.annotation.IdRes 32 | import androidx.annotation.LayoutRes 33 | import androidx.annotation.Px 34 | import androidx.annotation.StringRes 35 | import androidx.core.content.ContextCompat 36 | import androidx.core.graphics.drawable.DrawableCompat 37 | 38 | @Px internal fun Context.dimen(@DimenRes res: Int): Int { 39 | return resources.getDimensionPixelSize(res) 40 | } 41 | 42 | @ColorInt internal fun Context.color(@ColorRes res: Int): Int { 43 | return ContextCompat.getColor(this, res) 44 | } 45 | 46 | @ColorInt internal fun Context.colorAttr( 47 | @AttrRes attr: Int, 48 | fallback: Int = 0 49 | ): Int { 50 | val a = theme.obtainStyledAttributes(intArrayOf(attr)) 51 | try { 52 | return a.getColor(0, fallback) 53 | } finally { 54 | a.recycle() 55 | } 56 | } 57 | 58 | internal fun Context.string( 59 | @StringRes res: Int, 60 | vararg args: Any 61 | ): String = resources.getString(res, args) 62 | 63 | internal fun Drawable.tint(@ColorInt color: Int): Drawable { 64 | val wrapped = DrawableCompat.wrap(this) 65 | DrawableCompat.setTint(wrapped, color) 66 | return wrapped 67 | } 68 | 69 | internal fun Context.drawable(@DrawableRes res: Int): Drawable { 70 | return ContextCompat.getDrawable(this, res)!! 71 | } 72 | 73 | internal inline fun ViewPropertyAnimator.onAnimationEnd( 74 | crossinline continuation: (android.animation.Animator) -> Unit 75 | ) { 76 | setListener(object : AnimatorListenerAdapter() { 77 | override fun onAnimationEnd(animation: android.animation.Animator) { 78 | continuation(animation) 79 | setListener(null) 80 | } 81 | }) 82 | } 83 | 84 | @Suppress("DEPRECATION") 85 | internal inline fun View.onLayout(crossinline callback: (view: View) -> Unit) { 86 | viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { 87 | override fun onGlobalLayout() { 88 | callback(this@onLayout) 89 | viewTreeObserver.removeGlobalOnLayoutListener(this) 90 | } 91 | }) 92 | } 93 | 94 | internal fun Context.requireOneString( 95 | literal: String?, 96 | @StringRes res: Int?, 97 | vararg args: Any 98 | ): String { 99 | return when { 100 | literal != null -> literal 101 | res != null -> string(res) 102 | else -> throw IllegalStateException( 103 | "You must provide either a literal or resource value." 104 | ) 105 | } 106 | } 107 | 108 | internal fun Context.requireOneDimen( 109 | @Px literal: Int?, 110 | @DimenRes res: Int? 111 | ): Int { 112 | return when { 113 | literal != null -> literal 114 | res != null -> dimen(res) 115 | else -> throw IllegalStateException( 116 | "You must provide either a literal or resource value." 117 | ) 118 | } 119 | } 120 | 121 | internal fun Context.requireOneColor( 122 | @ColorInt literal: Int?, 123 | @ColorRes res: Int? 124 | ): Int { 125 | return when { 126 | literal != null -> literal 127 | res != null -> color(res) 128 | else -> throw IllegalStateException( 129 | "You must provide either a literal or resource value." 130 | ) 131 | } 132 | } 133 | 134 | internal fun Context.idName(@IdRes res: Int): String { 135 | return resources.getResourceName(res) 136 | } 137 | 138 | internal inline fun ViewGroup.inflate(@LayoutRes res: Int): T { 139 | return LayoutInflater.from(context).inflate(res, this, false) as T 140 | } 141 | 142 | internal fun View.removeSelf() { 143 | (parent as? ViewGroup)?.removeView(this) 144 | } 145 | -------------------------------------------------------------------------------- /library/src/main/res-public/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /library/src/main/res/drawable/mcab_nav_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /library/src/main/res/layout/mcab_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /library/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4dp 4 | 72dp 5 | 6 | -------------------------------------------------------------------------------- /sample.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/sample.apk -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply from: '../dependencies.gradle' 6 | 7 | android { 8 | compileSdkVersion versions.compileSdk 9 | buildToolsVersion versions.buildTools 10 | 11 | compileOptions { 12 | sourceCompatibility JavaVersion.VERSION_1_8 13 | targetCompatibility JavaVersion.VERSION_1_8 14 | } 15 | 16 | defaultConfig { 17 | applicationId "com.afollestad.materialcabsample" 18 | minSdkVersion versions.minSdk 19 | targetSdkVersion versions.compileSdk 20 | versionCode versions.publishVersionCode 21 | versionName versions.publishVersion 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation project(':library') 27 | 28 | implementation 'androidx.appcompat:appcompat:' + versions.androidx 29 | implementation 'androidx.recyclerview:recyclerview:' + versions.recyclerView 30 | 31 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin 32 | 33 | implementation 'com.afollestad:recyclical:' + versions.recyclical 34 | } 35 | 36 | apply from: '../spotless.gradle' -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/src/main/java/com/afollestad/materialcabsample/Items.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcabsample 17 | 18 | data class MainItem( 19 | var title: String 20 | ) 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/afollestad/materialcabsample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcabsample 17 | 18 | import android.os.Bundle 19 | import android.view.Menu 20 | import androidx.appcompat.app.AppCompatActivity 21 | import com.afollestad.materialcab.attached.AttachedCab 22 | import com.afollestad.materialcab.attached.destroy 23 | import com.afollestad.materialcab.attached.isActive 24 | import com.afollestad.materialcab.createCab 25 | import com.afollestad.recyclical.datasource.emptySelectableDataSource 26 | import com.afollestad.recyclical.setup 27 | import com.afollestad.recyclical.viewholder.isSelected 28 | import com.afollestad.recyclical.withItem 29 | import kotlinx.android.synthetic.main.activity_main.list 30 | 31 | /** @author Aidan Follestad (afollestad) */ 32 | class MainActivity : AppCompatActivity() { 33 | private val dataSource = emptySelectableDataSource().apply { 34 | onSelectionChange { invalidateCab() } 35 | } 36 | private var mainCab: AttachedCab? = null 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | setContentView(R.layout.activity_main) 41 | setSupportActionBar(findViewById(R.id.main_toolbar)) 42 | 43 | dataSource.set( 44 | IntArray(100) { it + 1 } 45 | .map { MainItem("Item #$it") } 46 | ) 47 | 48 | list.setup { 49 | withDataSource(dataSource) 50 | withItem(R.layout.listitem_main) { 51 | onBind(::MainViewHolder) { index, item -> 52 | itemView.isActivated = isSelected() 53 | title.text = item.title 54 | icon.setOnClickListener { 55 | dataSource.toggleSelectionAt(index) 56 | } 57 | } 58 | onClick { 59 | if (hasSelection()) { 60 | toggleSelection() 61 | } else { 62 | toast("Clicked $item") 63 | } 64 | } 65 | onLongClick { toggleSelection() } 66 | } 67 | } 68 | } 69 | 70 | private fun invalidateCab() { 71 | if (!dataSource.hasSelection()) { 72 | mainCab?.destroy() 73 | return 74 | } 75 | 76 | if (mainCab.isActive()) { 77 | mainCab?.apply { 78 | title(literal = getString(R.string.x_selected, dataSource.getSelectionCount())) 79 | } 80 | } else { 81 | mainCab = createCab(R.id.cab_stub) { 82 | title(literal = getString(R.string.x_selected, dataSource.getSelectionCount())) 83 | menu(R.menu.menu_cab) 84 | popupTheme(R.style.ThemeOverlay_AppCompat_Light) 85 | slideDown() 86 | 87 | onCreate { _, menu -> onCabCreated(menu) } 88 | onSelection { 89 | toast(it.title as String) 90 | true 91 | } 92 | onDestroy { 93 | dataSource.deselectAll() 94 | true 95 | } 96 | } 97 | } 98 | } 99 | 100 | private fun onCabCreated(menu: Menu): Boolean { 101 | // Makes the icons in the overflow menu visible 102 | if (menu.javaClass.simpleName == "MenuBuilder") { 103 | try { 104 | val field = menu.javaClass.getDeclaredField("mOptionalIconsVisible") 105 | field.isAccessible = true 106 | field.setBoolean(menu, true) 107 | } catch (ignored: Exception) { 108 | ignored.printStackTrace() 109 | } 110 | } 111 | return true // allow creation 112 | } 113 | 114 | override fun onBackPressed() { 115 | if (!mainCab.destroy()) { 116 | super.onBackPressed() 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /sample/src/main/java/com/afollestad/materialcabsample/Util.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcabsample 17 | 18 | import android.content.Context 19 | import android.widget.Toast 20 | 21 | private var toast: Toast? = null 22 | 23 | fun Context.toast(message: String) { 24 | toast?.cancel() 25 | toast = Toast.makeText(this, message, Toast.LENGTH_SHORT) 26 | .apply { 27 | show() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/src/main/java/com/afollestad/materialcabsample/ViewHolders.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.afollestad.materialcabsample 17 | 18 | import android.view.View 19 | import android.widget.TextView 20 | import com.afollestad.recyclical.ViewHolder 21 | 22 | class MainViewHolder(itemView: View) : ViewHolder(itemView) { 23 | val icon: View = itemView.findViewById(R.id.icon) 24 | val title: TextView = itemView.findViewById(R.id.title) 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v21/list_ripple_selector.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_action_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_action_cut.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_action_paste.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_action_selectall.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_action_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/list_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 26 | 27 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/listitem_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/menu_cab.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 24 | 25 | 30 | 31 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afollestad/material-cab/c053373ae56af32ff097d902a0f6911a56d71559/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #26C6DA 5 | #00ACC1 6 | #1DE9B6 7 | 8 | #3e000000 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Material Cab Demo 4 | Share 5 | Cut 6 | Copy 7 | Paste 8 | Select All 9 | 10 | %1$d selected 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':library' 2 | -------------------------------------------------------------------------------- /spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.diffplug.gradle.spotless" 2 | spotless { 3 | java { 4 | target "**/*.java" 5 | trimTrailingWhitespace() 6 | removeUnusedImports() 7 | googleJavaFormat() 8 | endWithNewline() 9 | } 10 | kotlin { 11 | target "**/*.kt" 12 | ktlint().userData(['indent_size': '2', 'continuation_indent_size': '2']) 13 | licenseHeaderFile '../spotless.license.kt' 14 | trimTrailingWhitespace() 15 | endWithNewline() 16 | } 17 | } -------------------------------------------------------------------------------- /spotless.license.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Designed and developed by Aidan Follestad (@afollestad) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /versionsPlugin.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.github.ben-manes.versions" 2 | 3 | dependencyUpdates.resolutionStrategy { 4 | componentSelection { rules -> 5 | rules.all { ComponentSelection selection -> 6 | boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm'].any { qualifier -> 7 | selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ 8 | } 9 | if (rejected) { 10 | selection.reject('Not stable') 11 | } 12 | } 13 | } 14 | } 15 | --------------------------------------------------------------------------------