├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── project
├── .DS_Store
├── Libraries.scala
├── ReplacePropertiesGenerator.scala
├── Settings.scala
├── Versions.scala
├── build.properties
└── plugins.sbt
├── scripts
└── runPackage.sh
└── src
├── .DS_Store
├── main
├── .DS_Store
├── AndroidManifest.xml
├── assets
│ └── activities.json
├── res
│ ├── drawable-hdpi
│ │ ├── ic_add.png
│ │ └── icon_app.png
│ ├── drawable-mdpi
│ │ └── icon_app.png
│ ├── drawable-v21
│ │ ├── background_default_fab.xml
│ │ └── background_default_list.xml
│ ├── drawable-xhdpi
│ │ ├── ic_add.png
│ │ └── icon_app.png
│ ├── drawable-xxhdpi
│ │ ├── forecast_01.png
│ │ ├── forecast_02.png
│ │ ├── forecast_03.png
│ │ ├── forecast_04.png
│ │ ├── forecast_09.png
│ │ ├── forecast_10.png
│ │ ├── forecast_11.png
│ │ ├── forecast_13.png
│ │ ├── forecast_50.png
│ │ ├── ic_add.png
│ │ ├── icon_app.png
│ │ ├── map_marker.png
│ │ └── unknown.png
│ ├── drawable-xxxhdpi
│ │ └── icon_app.png
│ ├── drawable
│ │ ├── background_default_fab.xml
│ │ ├── background_default_fab_circle.xml
│ │ ├── background_default_fab_shadow.xml
│ │ ├── background_default_list.xml
│ │ ├── background_error_button.xml
│ │ ├── background_item_api_advised.xml
│ │ ├── background_item_api_required.xml
│ │ ├── background_item_api_success.xml
│ │ ├── background_item_circle.xml
│ │ ├── background_item_circle_activated.xml
│ │ ├── background_item_circle_pressed.xml
│ │ ├── background_item_icon_selector.xml
│ │ ├── background_item_pathmorphsample_1.xml
│ │ ├── background_item_pathmorphsample_2.xml
│ │ ├── background_item_pathmorphsample_3.xml
│ │ ├── background_item_pathmorphsample_4.xml
│ │ ├── background_item_square.xml
│ │ └── placeholder_circle.xml
│ ├── layout
│ │ ├── image_item.xml
│ │ └── material_list_activity.xml
│ ├── menu
│ │ ├── activity_forecast.xml
│ │ └── activity_google_maps.xml
│ ├── values-v21
│ │ ├── colors.xml
│ │ └── themes.xml
│ └── values
│ │ ├── app_config.xml
│ │ ├── colors.xml
│ │ ├── config.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
├── resources
│ └── application.conf
└── scala
│ ├── .DS_Store
│ └── com
│ ├── .DS_Store
│ └── fortysevendeg
│ ├── .DS_Store
│ └── scala
│ ├── .DS_Store
│ └── android
│ ├── .DS_Store
│ ├── modules
│ ├── forecast
│ │ ├── Messages.scala
│ │ ├── impl
│ │ │ └── ForecastServices.scala
│ │ └── model
│ │ │ ├── ApiModel.scala
│ │ │ ├── Conversions.scala
│ │ │ └── Forecast.scala
│ └── utils
│ │ └── NetUtils.scala
│ └── ui
│ ├── akkasimon
│ ├── AkkaSimonActivity.scala
│ ├── Layout.scala
│ ├── Styles.scala
│ ├── actors
│ │ ├── ColorActor.scala
│ │ └── ComputerActor.scala
│ ├── fragments
│ │ ├── ColorFragment.scala
│ │ └── ComputerFragment.scala
│ └── util
│ │ ├── FragmentEnum.scala
│ │ └── SimonAkkaFragment.scala
│ ├── apirequest
│ ├── ForecastApiRequestActivity.scala
│ ├── ForecastFragment.scala
│ ├── Layout.scala
│ └── Styles.scala
│ ├── circularreveal
│ ├── CircularRevealActivity.scala
│ ├── Layout.scala
│ ├── SampleFragment.scala
│ └── Styles.scala
│ ├── commons
│ ├── CommonsStyles.scala
│ ├── JsonReads.scala
│ ├── ScalaExtraTweaks.scala
│ └── ToolbarLayout.scala
│ ├── components
│ ├── CircleView.scala
│ ├── CircularTransformation.scala
│ ├── PathMorphDrawable.scala
│ └── RippleBackgroundView.scala
│ ├── googlemaps
│ ├── CustomMapFragment.scala
│ ├── GoogleMapsActivity.scala
│ ├── Layout.scala
│ ├── Styles.scala
│ └── googlemaps.scala
│ ├── main
│ ├── Layout.scala
│ ├── MainActivity.scala
│ ├── MainItemDecorator.scala
│ ├── ProjectActivityInfoListAdapter.scala
│ └── Styles.scala
│ ├── materiallist
│ ├── Composers.scala
│ ├── FABAnimationBehavior.scala
│ ├── ImageListAdapter.scala
│ └── MaterialListActivity.scala
│ ├── pathmorphing
│ ├── Layout.scala
│ ├── PathMorphingActivity.scala
│ └── Styles.scala
│ ├── ripplebackground
│ ├── Layout.scala
│ ├── RippleBackgroundActivity.scala
│ └── Styles.scala
│ └── textstyles
│ ├── Layout.scala
│ ├── Styles.scala
│ └── TextStylesActivity.scala
└── test
├── resources
└── weather.json
└── scala
└── com
└── fortysevendeg
└── scala
└── android
├── BaseTestSpecification.scala
├── ContextWrapperContextTestSupport.scala
└── modules
└── forecast
├── ForecastServicesSpec.scala
└── model
└── JsonModelSpec.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | logs
3 | target
4 | tmp
5 | .history
6 | dist
7 | /out
8 | /RUNNING_PID
9 | /.ivy*
10 |
11 | # sbt specific
12 | /.sbt
13 | .cache/
14 | .history/
15 | .lib/
16 | dist/*
17 | target/
18 | lib_managed/
19 | src_managed/
20 | project/boot/
21 | project/plugins/project/
22 | project/project
23 | project/target
24 | /.activator
25 |
26 | # Scala-IDE specific
27 | .scala_dependencies
28 | .worksheet
29 |
30 | #Eclipse specific
31 | .classpath
32 | .project
33 | .cache
34 | .settings/
35 |
36 | #IntelliJ IDEA specific
37 | .idea/
38 | /.idea_modules
39 | /.idea
40 | /*.iml
41 |
42 | #Proguard
43 | proguard-sbt.txt
44 |
45 | #Properties
46 | local.properties
47 | debug.properties
48 | release.properties
49 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | scala:
3 | - 2.11.7
4 | before_install:
5 | - sudo apt-get update -qq
6 | - sudo apt-get install libc6-i386 lib32z1 lib32stdc++6
7 | - wget http://dl.google.com/android/android-sdk_r24-linux.tgz
8 | - tar xf android-sdk_r24-linux.tgz
9 | - export ANDROID_HOME=$PWD/android-sdk-linux
10 | - export ANDROID_SDK_HOME=$PWD/android-sdk-linux
11 | - export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
12 | - echo yes | android update sdk --all --filter platform-tools --no-ui
13 | - echo yes | android update sdk --all --filter build-tools-23.0.1 --no-ui
14 | - echo yes | android update sdk --all --filter android-23 --no-ui
15 | - echo yes | android update sdk --all --filter extra-android-support --no-ui
16 | - echo yes | android update sdk --all --filter extra-android-m2repository --no-ui
17 | - echo yes | android update sdk --all --filter extra-google-m2repository --no-ui
18 | - echo yes | android update sdk --all --filter extra-google-google_play_services --no-ui
19 | script:
20 | - sbt ++$TRAVIS_SCALA_VERSION test
21 | - ./scripts/runPackage.sh
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/47deg/scala-android)
2 |
3 | Scala API Demos
4 | =============
5 |
6 | This repository contains examples of using Scala on Android. The [Macroid](http://macroid.github.io/) library is used in the project to assist in GUI operations.
7 |
8 | You can download the project from [Google Play](https://play.google.com/store/apps/details?id=com.fortysevendeg.scala.android)
9 |
10 | Compile
11 | ======
12 |
13 | You can compile this project and contribute improvements. To compile the project:
14 |
15 | * Download [Activator](https://www.lightbend.com/community/core-tools/activator-and-sbt) and install it
16 | * Configure the Android SDK on your computer
17 | * Clone this GitHub project to your computer
18 | * From project root directory run:
19 |
20 | ```
21 | $ activator
22 | ```
23 | or
24 |
25 | ```
26 | $ sbt
27 | ```
28 |
29 | * Connect your phone and execute:
30 |
31 | ```
32 | > run
33 | ```
34 |
35 | You can use your favorite IDE. At 47 Degrees we use IntelliJ with the Scala plugin. If you want to run this project from IntelliJ you only need to import the project.
36 |
37 | Add Debug Keys
38 | ========
39 |
40 | You need to add a `debug.properties` file to the root project with the necessary keys to compile. The content should be:
41 |
42 | ```
43 | openweather.api.key=***
44 | google.map.key=***
45 | ```
46 |
47 | Contribute your own examples
48 | ===============
49 |
50 | If you want to learn *Scala on Android* and you want to share your examples, you can send us a PR with your new feature.
51 |
52 | Follow these steps to create your example:
53 |
54 | * Create a new package inside `ui` for your sample
55 | * The package for a feature contains all of the UI information (it's not necessary but you should consider it). This information is:
56 | * Activities
57 | * Fragments
58 | * Adapters
59 | * Styles: this file replaces the XML Resources. All styles are defined in this file
60 | * Layouts: this file replaces the XML Resources. All layouts are defined in this file
61 | * Add your activity to `AndroidManifest.xml`
62 | * Create a new node in `activities.json` inside the `asset` directory. The JSON should look like this:
63 |
64 | ```
65 | {
66 | "name": "Name of your example",
67 | "description": "Description of your example",
68 | "className": "Path of your activity",
69 | "minApi": "An integer designating the minimum API Level required ",
70 | "targetApi": "An integer designating the API Level that the application targets",
71 | "scalaLevel": "An integer designating the Scala Level of your example [1,2,3]",
72 | "androidLevel": "An integer designating the Android Level of your example [1,2,3]",
73 | "user" : {
74 | "avatar": "Your avatar URL",
75 | "name": "Your name",
76 | "twitter": "Your twitter username"
77 | }
78 | }
79 | ```
80 |
81 | ```
82 | 1 -> Beginner
83 | 2 -> Intermediate
84 | 3 -> Advanced
85 | ```
86 |
87 | If you are having trouble deciding on an example to contribute here are some ideas:
88 |
89 | * Transitions between activities: use the new [Activity Transitions in Material Design](https://developer.android.com/training/material/animations.html#Transitions), similar to *Google Play Music*
90 | * Validate forms with [Validation in ScalaZ](http://eed3si9n.com/learning-scalaz/Validation.html)
91 |
92 |
93 | License
94 | ======
95 |
96 | Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
97 |
98 | 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
99 |
100 | http://www.apache.org/licenses/LICENSE-2.0
101 | 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.
102 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import Libraries.android._
2 | import Libraries.macroid._
3 | import Libraries.akka._
4 | import Libraries.playServices._
5 | import Libraries.graphics._
6 | import Libraries.json._
7 | import Libraries.net._
8 | import Libraries.test._
9 | import ReplacePropertiesGenerator._
10 | import android.PromptPasswordsSigningConfig
11 |
12 | android.Plugin.androidBuild
13 |
14 | platformTarget in Android := Versions.androidPlatformV
15 |
16 | name := "scala-android"
17 |
18 | organization := "com.fortysevendeg"
19 |
20 | organizationName := "47 Degrees"
21 |
22 | organizationHomepage := Some(new URL("http://47deg.com"))
23 |
24 | version := Versions.appV
25 |
26 | scalaVersion := Versions.scalaV
27 |
28 | scalacOptions ++= Seq("-feature", "-deprecation")
29 |
30 | javacOptions ++= Seq("-source", "1.7", "-target", "1.7")
31 |
32 | scalacOptions ++= Seq("-feature", "-deprecation", "-target:jvm-1.7")
33 |
34 | resolvers ++= Settings.resolvers
35 |
36 | libraryDependencies ++= Seq(
37 | aar(macroidRoot),
38 | aar(macroidAkkaFragments),
39 | aar(androidDesign),
40 | aar(androidCardView),
41 | aar(androidRecyclerview),
42 | aar(macroidExtras),
43 | aar(playServicesMaps),
44 | playJson,
45 | picasso,
46 | communicator,
47 | akkaActor,
48 | specs2,
49 | mockito,
50 | androidTest)
51 |
52 | transitiveAndroidLibs in Android := true
53 |
54 | run <<= run in Android
55 |
56 | apkSigningConfig in Android := Option(
57 | PromptPasswordsSigningConfig(
58 | keystore = new File(Path.userHome.absolutePath + "/.android/signed.keystore"),
59 | alias = "47deg"))
60 |
61 | proguardScala in Android := true
62 |
63 | useProguard in Android := true
64 |
65 | useProguardInDebug in Android := true
66 |
67 | proguardCache in Android := Seq.empty
68 |
69 | proguardOptions in Android ++= Settings.proguardCommons ++ Settings.proguardAkka
70 |
71 | packagingOptions in Android := PackagingOptions(
72 | Seq("META-INF/LICENSE",
73 | "META-INF/LICENSE.txt",
74 | "META-INF/NOTICE",
75 | "META-INF/NOTICE.txt"))
76 |
77 | dexMaxHeap in Android := "2048m"
78 |
79 | dexMulti in Android := true
80 |
81 | packageRelease <<= (packageRelease in Android).dependsOn(setDebugTask(false))
82 |
83 | packageResources in Android <<= (packageResources in Android).dependsOn(replaceValuesTask)
84 |
--------------------------------------------------------------------------------
/project/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/project/.DS_Store
--------------------------------------------------------------------------------
/project/Libraries.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 |
3 | object Libraries {
4 |
5 | def onCompile(dep: ModuleID): ModuleID = dep % "compile"
6 | def onTest(dep: ModuleID): ModuleID = dep % "test"
7 |
8 | object scala {
9 |
10 | lazy val scalaReflect = "org.scala-lang" % "scala-reflect" % Versions.scalaV
11 | lazy val scalap = "org.scala-lang" % "scalap" % Versions.scalaV
12 | }
13 |
14 | object android {
15 |
16 | def androidDep(module: String) = "com.android.support" % module % Versions.androidV
17 |
18 | lazy val androidRecyclerview = androidDep("recyclerview-v7")
19 | lazy val androidCardView = androidDep("cardview-v7")
20 | lazy val androidDesign = androidDep("design")
21 | }
22 |
23 | object playServices {
24 |
25 | def playServicesDep(module: String) = "com.google.android.gms" % module % Versions.playServicesV
26 |
27 | lazy val playServicesGooglePlus = playServicesDep("play-services-plus")
28 | lazy val playServicesAccountLogin = playServicesDep("play-services-identity")
29 | lazy val playServicesActivityRecognition = playServicesDep("play-services-location")
30 | lazy val playServicesAppIndexing = playServicesDep("play-services-appindexing")
31 | lazy val playServicesCast = playServicesDep("play-services-cast")
32 | lazy val playServicesDrive = playServicesDep("play-services-drive")
33 | lazy val playServicesFit = playServicesDep("play-services-fitness")
34 | lazy val playServicesMaps = playServicesDep("play-services-maps")
35 | lazy val playServicesAds = playServicesDep("play-services-ads")
36 | lazy val playServicesPanoramaViewer = playServicesDep("play-services-panorama")
37 | lazy val playServicesGames = playServicesDep("play-services-games")
38 | lazy val playServicesWallet = playServicesDep("play-services-wallet")
39 | lazy val playServicesWear = playServicesDep("play-services-wearable")
40 | // Google Actions, Google Analytics and Google Cloud Messaging
41 | lazy val playServicesBase = playServicesDep("play-services-base")
42 | }
43 |
44 | object graphics {
45 | lazy val picasso = "com.squareup.picasso" % "picasso" % Versions.picassoV
46 | }
47 |
48 | object akka {
49 |
50 | def akka(module: String) = "com.typesafe.akka" %% s"akka-$module" % Versions.akkaV
51 |
52 | lazy val akkaActor = akka("actor")
53 | lazy val akkaTestKit = akka("testkit")
54 |
55 | }
56 |
57 | object macroid {
58 |
59 | def macroid(module: String = "") =
60 | "org.macroid" %% s"macroid${if(!module.isEmpty) s"-$module" else ""}" % Versions.macroidV
61 |
62 | lazy val macroidRoot = macroid()
63 | lazy val macroidAkkaFragments = macroid("akka")
64 | lazy val macroidExtras = "com.fortysevendeg" %% "macroid-extras" % Versions.macroidExtras
65 | }
66 |
67 | object json {
68 | lazy val playJson = "com.typesafe.play" %% "play-json" % Versions.playJsonV
69 | }
70 |
71 | object net {
72 | lazy val communicator = "io.taig" %% "communicator" % Versions.communicatorV
73 |
74 | }
75 |
76 | object test {
77 | lazy val specs2 = "org.specs2" %% "specs2-core" % Versions.specs2V % "test"
78 | lazy val androidTest = "com.google.android" % "android" % "4.1.1.4" % "test"
79 | lazy val mockito = "org.specs2" % "specs2-mock_2.11" % Versions.mockitoV % "test"
80 | }
81 | }
--------------------------------------------------------------------------------
/project/ReplacePropertiesGenerator.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | import java.io.{File, FileInputStream}
18 | import java.util.Properties
19 |
20 | import android.Keys._
21 | import sbt._
22 |
23 | import scala.annotation.tailrec
24 | import scala.collection.JavaConverters._
25 |
26 | object ReplacePropertiesGenerator {
27 |
28 | val debugPropertiesFile = "debug.properties"
29 |
30 | val releasePropertiesFile = "release.properties"
31 |
32 | var debug = true
33 |
34 | def propertiesMap(): Map[String, String] = {
35 | (loadPropertiesFile map { file =>
36 | val properties = new Properties()
37 | properties.load(new FileInputStream(file))
38 | properties.asScala.toMap
39 | }) getOrElse Map.empty
40 | }
41 |
42 | private def namePropertyInConfig(name: String) = s"$${$name}"
43 |
44 | private def loadPropertiesFile: Option[File] = {
45 | val file = new File(if (debug) debugPropertiesFile else releasePropertiesFile)
46 | if (file.exists()) Some(file) else None
47 | }
48 |
49 | def replaceContent(valuesFile: File) = {
50 | val properties = propertiesMap()
51 | val content = IO.readLines(valuesFile) map (replaceLine(properties, _))
52 | IO.write(valuesFile, content.mkString("\n"))
53 | }
54 |
55 | private def replaceLine(properties: Map[String, String], line: String) = {
56 | @tailrec
57 | def replace(properties: Map[String, String], line: String): String = {
58 | if (properties.isEmpty) {
59 | line
60 | } else {
61 | val (key, value) = properties.head
62 | val name = namePropertyInConfig(key)
63 | replace(properties.tail, if (line.contains(name)) line.replace(name, value) else line)
64 | }
65 | }
66 | replace(properties, line)
67 | }
68 |
69 | def replaceValuesTask = Def.task[Seq[File]] {
70 | try {
71 | val dir: (File, File) = (collectResources in Android).value
72 | val valuesFile: File = new File(dir._2, "/values/values.xml")
73 | replaceContent(valuesFile)
74 | Seq(valuesFile)
75 | } catch {
76 | case e: Throwable =>
77 | println("An error occurred loading values.xml")
78 | throw e
79 | }
80 | }
81 |
82 | def setDebugTask(debug: Boolean) = Def.task[Unit] {
83 | this.debug = debug
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/project/Settings.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 |
3 | object Settings {
4 |
5 | lazy val resolvers =
6 | Seq(
7 | Resolver.mavenLocal,
8 | DefaultMavenRepository,
9 | "jcenter" at "http://jcenter.bintray.com",
10 | "47 Degrees Bintray Repo" at "http://dl.bintray.com/47deg/maven",
11 | Resolver.typesafeRepo("releases"),
12 | Resolver.typesafeRepo("snapshots"),
13 | Resolver.typesafeIvyRepo("snapshots"),
14 | Resolver.sonatypeRepo("releases"),
15 | Resolver.sonatypeRepo("snapshots"),
16 | Resolver.defaultLocal,
17 | "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
18 | )
19 |
20 | lazy val proguardCommons = Seq(
21 | "-ignorewarnings",
22 | "-keep class scala.Dynamic",
23 | "-keep class com.fortysevendeg.scala.android.** { *; }",
24 | "-keep class macroid.** { *; }",
25 | "-keep class android.** { *; }")
26 |
27 | lazy val proguardAkka = Seq(
28 | "-keep class akka.actor.Actor$class { *; }",
29 | "-keep class akka.actor.LightArrayRevolverScheduler { *; }",
30 | "-keep class akka.actor.LocalActorRefProvider { *; }",
31 | "-keep class akka.actor.CreatorFunctionConsumer { *; }",
32 | "-keep class akka.actor.TypedCreatorFunctionConsumer { *; }",
33 | "-keep class akka.dispatch.BoundedDequeBasedMessageQueueSemantics { *; }",
34 | "-keep class akka.dispatch.UnboundedMessageQueueSemantics { *; }",
35 | "-keep class akka.dispatch.UnboundedDequeBasedMessageQueueSemantics { *; }",
36 | "-keep class akka.dispatch.DequeBasedMessageQueueSemantics { *; }",
37 | "-keep class akka.dispatch.MultipleConsumerSemantics { *; }",
38 | "-keep class akka.actor.LocalActorRefProvider$Guardian { *; }",
39 | "-keep class akka.actor.LocalActorRefProvider$SystemGuardian { *; }",
40 | "-keep class akka.dispatch.UnboundedMailbox { *; }",
41 | "-keep class akka.actor.DefaultSupervisorStrategy { *; }",
42 | "-keep class macroid.akkafragments.AkkaAndroidLogger { *; }",
43 | "-keep class akka.event.Logging$LogExt { *; }")
44 | }
45 |
--------------------------------------------------------------------------------
/project/Versions.scala:
--------------------------------------------------------------------------------
1 | object Versions {
2 |
3 | val appV = "1.0.0"
4 | val scalaV = "2.11.7"
5 | val androidPlatformV = "android-23"
6 | val androidV = "23.0.1"
7 | val macroidExtras = "0.2"
8 | val macroidV = "2.0.0-20150427"
9 | val akkaV = "2.3.6"
10 | val playServicesV = "6.5.87"
11 | val playJsonV = "2.3.4"
12 | val picassoV = "2.5.0"
13 | val specs2V = "3.6.1"
14 | val mockitoV = "3.6.1"
15 | val communicatorV = "2.0.0"
16 | }
17 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.9
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | logLevel := Level.Info
2 | addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.0")
3 |
--------------------------------------------------------------------------------
/scripts/runPackage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ev
3 | sbt ++$TRAVIS_SCALA_VERSION clean
4 | sbt ++$TRAVIS_SCALA_VERSION android:package
5 | sbt ++$TRAVIS_SCALA_VERSION android:package
6 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/.DS_Store
--------------------------------------------------------------------------------
/src/main/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/.DS_Store
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
24 |
25 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/main/assets/activities.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Material Design List",
4 | "description": "This example shows how to use a list with the AppBar and FabButton from the Android Support Design Library",
5 | "className": "com.fortysevendeg.scala.android.ui.materiallist.MaterialListActivity",
6 | "minApi": 14,
7 | "targetApi": 21,
8 | "scalaLevel": 1,
9 | "androidLevel": 2,
10 | "user" : {
11 | "avatar": "http://www.47deg.com/assets/img/company/profile-javi.jpg",
12 | "name": "Javi Pacheco",
13 | "twitter": "@javielinux"
14 | }
15 | },
16 | {
17 | "name": "Ripple Background",
18 | "description": "This example demonstrates usage of the new Ripple animations available in Lollipop and how to create several animations in serial using Macroid",
19 | "className": "com.fortysevendeg.scala.android.ui.ripplebackground.RippleBackgroundActivity",
20 | "minApi": 21,
21 | "targetApi": 21,
22 | "scalaLevel": 1,
23 | "androidLevel": 2,
24 | "user" : {
25 | "avatar": "http://www.47deg.com/assets/img/company/profile-javi.jpg",
26 | "name": "Javi Pacheco",
27 | "twitter": "@javielinux"
28 | }
29 | },
30 | {
31 | "name": "Path Morphing",
32 | "description": "This example demonstrates a new drawable created for this project called PathMorphDrawable, used for path segment transformations in Canvas",
33 | "className": "com.fortysevendeg.scala.android.ui.pathmorphing.PathMorphingActivity",
34 | "minApi": 14,
35 | "targetApi": 14,
36 | "scalaLevel": 2,
37 | "androidLevel": 2,
38 | "user" : {
39 | "avatar": "http://www.47deg.com/assets/img/company/profile-paco.jpg",
40 | "name": "Francisco Díaz",
41 | "twitter": "@francisco_dr"
42 | }
43 | },
44 | {
45 | "name": "Circular Reveal",
46 | "description": "This example demonstrates Lollipop's new Circular Reveal and how it can be used in a Fragment. On non Lollipop devices it will fallback to using a fade animation",
47 | "className": "com.fortysevendeg.scala.android.ui.circularreveal.CircularRevealActivity",
48 | "minApi": 14,
49 | "targetApi": 21,
50 | "scalaLevel": 1,
51 | "androidLevel": 1,
52 | "user" : {
53 | "avatar": "http://www.47deg.com/assets/img/company/profile-javi.jpg",
54 | "name": "Javi Pacheco",
55 | "twitter": "@javielinux"
56 | }
57 | },
58 | {
59 | "name": "Google Maps",
60 | "description": "A simple example of using Google Maps in a project. Includes - changing map type and adding markers",
61 | "className": "com.fortysevendeg.scala.android.ui.googlemaps.GoogleMapsActivity",
62 | "minApi": 14,
63 | "targetApi": 14,
64 | "scalaLevel": 1,
65 | "androidLevel": 2,
66 | "user" : {
67 | "avatar": "http://www.47deg.com/assets/img/company/profile-fede.jpg",
68 | "name": "Fede Fernández",
69 | "twitter": "@fede_fdz"
70 | }
71 | },
72 | {
73 | "name": "Text Styles",
74 | "description": "A basic example to demonstrate project setup and define layouts and styles using Macroid",
75 | "className": "com.fortysevendeg.scala.android.ui.textstyles.TextStylesActivity",
76 | "minApi": 14,
77 | "targetApi": 14,
78 | "scalaLevel": 1,
79 | "androidLevel": 1,
80 | "user" : {
81 | "avatar": "http://www.47deg.com/assets/img/company/profile-javi.jpg",
82 | "name": "Javi Pacheco",
83 | "twitter": "@javielinux"
84 | }
85 | },
86 | {
87 | "name": "Akka Simon",
88 | "description": "Using the classic game Simon, this example demonstrates the use of Akka to communicate between fragments",
89 | "className": "com.fortysevendeg.scala.android.ui.akkasimon.AkkaSimonActivity",
90 | "minApi": 14,
91 | "targetApi": 14,
92 | "scalaLevel": 3,
93 | "androidLevel": 2,
94 | "user" : {
95 | "avatar": "http://www.47deg.com/assets/img/company/profile-juanpe.jpg",
96 | "name": "Juan Pedro Moreno",
97 | "twitter": "@juanpedromoreno"
98 | }
99 | },
100 | {
101 | "name": "Forecast API Request",
102 | "description": "This example demonstrates how to call an endpoint with the use of Future, parse the json from the response and show the requested data in a view",
103 | "className": "com.fortysevendeg.scala.android.ui.apirequest.ForecastApiRequestActivity",
104 | "minApi": 14,
105 | "targetApi": 14,
106 | "scalaLevel": 2,
107 | "androidLevel": 1,
108 | "user" : {
109 | "avatar": "http://www.47deg.com/assets/img/company/profile-fede.jpg",
110 | "name": "Fede Fernández",
111 | "twitter": "@fede_fdz"
112 | }
113 | }
114 | ]
115 |
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-hdpi/ic_add.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/icon_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-hdpi/icon_app.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/icon_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-mdpi/icon_app.png
--------------------------------------------------------------------------------
/src/main/res/drawable-v21/background_default_fab.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 | -
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/main/res/drawable-v21/background_default_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 | -
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xhdpi/ic_add.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/icon_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xhdpi/icon_app.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_01.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_02.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_03.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_04.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_09.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_10.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_11.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_13.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/forecast_50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/forecast_50.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/ic_add.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/icon_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/icon_app.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/map_marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/map_marker.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxhdpi/unknown.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/icon_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/res/drawable-xxxhdpi/icon_app.png
--------------------------------------------------------------------------------
/src/main/res/drawable/background_default_fab.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
26 |
32 |
33 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_default_fab_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_default_fab_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_default_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_error_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_api_advised.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_api_required.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_api_success.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_circle_activated.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_circle_pressed.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_icon_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_pathmorphsample_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_pathmorphsample_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_pathmorphsample_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_pathmorphsample_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_item_square.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/placeholder_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/res/layout/image_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/res/layout/material_list_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
16 |
17 |
22 |
23 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/res/menu/activity_forecast.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/res/menu/activity_google_maps.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/res/values-v21/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 |
--------------------------------------------------------------------------------
/src/main/res/values-v21/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/src/main/res/values/app_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ${google.map.key}
4 | http://api.openweathermap.org/data/2.5/weather?lat=%1$s&lon=%2$s&units=metric
5 | x-api-key
6 | ${openweather.api.key}
7 | https://twitter.com/%s
8 |
--------------------------------------------------------------------------------
/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #2D4053
6 | #ff192533
7 | #F2554A
8 | #ff49cb94
9 |
10 | #E4E9EB
11 |
12 | #ff49cb94
13 | #ff3da97c
14 | #46000000
15 |
16 | #11000000
17 | #22000000
18 |
19 |
20 | #F2554A
21 | #E5B838
22 | #18B491
23 | #ECF0F1
24 | #b92d4053
25 | #ffffff
26 | #802d4053
27 | #802d4053
28 | #E0E3E6
29 |
30 |
31 | #F44336
32 | #9C27B0
33 | #03A9F4
34 |
35 |
36 | #ffe6e6e6
37 | #ffd6d6d6
38 | #ffb6b6b6
39 | #ff868686
40 | #ffa6a6a6
41 | #ff696969
42 | #2E2E2E
43 | #545756
44 | #72979C
45 | #EDB55A
46 |
47 |
48 | #87364550
49 | #ffffff
50 | #E05F5E
51 | #ffc45453
52 |
--------------------------------------------------------------------------------
/src/main/res/values/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | http://lorempixel.com/600/600/technics/
4 |
--------------------------------------------------------------------------------
/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 3dp
6 | 2dp
7 | 4dp
8 | 8dp
9 | 8dp
10 | 16dp
11 |
12 | 2dp
13 | 4dp
14 |
15 | 1dp
16 |
17 | 10sp
18 | 12sp
19 | 14sp
20 | 16sp
21 | 20sp
22 | 24sp
23 |
24 | 56dp
25 |
26 |
27 | 32dp
28 | 60dp
29 |
30 |
31 | 2dp
32 | 64dp
33 |
34 | 200dp
35 |
36 |
37 | 132dp
38 | 48dp
39 |
40 |
41 | 240dp
42 | 64dp
43 |
44 |
45 | 24sp
46 | 60sp
47 | 18sp
48 | 14sp
49 | 160dp
50 |
51 |
52 | 200dp
53 |
54 |
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scala API Demos
5 | Ripple Background
6 | Text Styles
7 | Circular Reveal
8 | Material Design List
9 | Path Morphing
10 | Select icon
11 | Select color
12 | Stroke: %1$s dp
13 | Size: %1$s x %2$s
14 | Google Maps
15 | 47 Degrees
16 | Reactive, scalable software solutions.
17 | Sample Marker
18 | This is a sample marker
19 | Satellite
20 | Normal
21 | Hybrid
22 | Terrain
23 |
24 | Scala:
25 | Android:
26 |
27 | Beginner
28 | Intermediate
29 | Advanced
30 |
31 | You need an Android version greater than
32 | API %s
33 | Add Marker
34 | Clear Map
35 |
36 | Activity not found
37 | JSON file not found
38 | Malformed JSON file
39 | An unexpected error has occurred
40 |
41 | Lorem ipsum dolor sit amet, vix vocent suscipit no. Definitionem delicatissimi at pri. Pro omnis civibus in, oporteat senserit instructior eos in. Ne usu tale definitionem, quo consetetur temporibus definitionem et, assum putent dolores mei in. No aliquam detraxit pro, quem eloquentiam id pro.
42 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.
43 |
44 |
45 | Akka Simon
46 | Welcome to\nAkka Simon
47 | Your Score\n%s
48 | Go!
49 | Round
50 | Round: %s
51 |
52 |
53 | Technics
54 |
55 |
56 | About
57 |
58 | The weather source of this demo is OpenWeatherMap
59 | \n\nAll data provided by OpenWeatherMap is distributed under terms of the Creative Commons license http://creativecommons.org/licenses/by-sa/2.0/.
60 | \n\n\nIcons created by Yannick (http://www.flaticon.com/authors/yannick) from http://www.flaticon.com.
61 | \n\nLicensed under Creative Commons BY 3.0 (http://creativecommons.org/licenses/by/3.0/)
62 | Forecast API Request
63 | Try again
64 | Waiting for a valid location
65 | Loading weather
66 | Too long? Try a sample location
67 | Unknown error
68 | Error getting location
69 | Error loading weather
70 |
71 |
72 | Add new item
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | akka {
2 | loggers = ["macroid.akka.AkkaAndroidLogger"]
3 | }
--------------------------------------------------------------------------------
/src/main/scala/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/scala/.DS_Store
--------------------------------------------------------------------------------
/src/main/scala/com/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/scala/com/.DS_Store
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/scala/com/fortysevendeg/.DS_Store
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/scala/com/fortysevendeg/scala/.DS_Store
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xebia-functional/scala-android/fa7222921dc79620e7410fab84b7cb4af86a19c2/src/main/scala/com/fortysevendeg/scala/android/.DS_Store
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/forecast/Messages.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | package com.fortysevendeg.scala.android.modules.forecast
18 |
19 | import com.fortysevendeg.scala.android.modules.forecast.model.Forecast
20 |
21 | case class ForecastRequest(latitude: Double, longitude: Double)
22 |
23 | case class ForecastResponse(forecastMaybe: Option[Forecast])
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/forecast/impl/ForecastServices.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | package com.fortysevendeg.scala.android.modules.forecast.impl
18 |
19 | import java.io.InputStream
20 |
21 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
22 | import com.fortysevendeg.scala.android.R
23 | import com.fortysevendeg.scala.android.modules.forecast.model._
24 | import com.fortysevendeg.scala.android.modules.forecast.{ForecastRequest, ForecastResponse}
25 | import com.fortysevendeg.scala.android.modules.utils.NetUtils
26 | import io.taig.communicator.response.Plain
27 | import io.taig.communicator.result.Parser
28 | import macroid.ContextWrapper
29 | import play.api.libs.json.Json
30 |
31 | import scala.concurrent.Future
32 |
33 | trait ApiReads {
34 |
35 | implicit val apiCloudsReads = Json.reads[ApiClouds]
36 | implicit val apiWindReads = Json.reads[ApiWind]
37 | implicit val apiWeatherReads = Json.reads[ApiWeather]
38 | implicit val apiMainReads = Json.reads[ApiMain]
39 | implicit val apiSysReads = Json.reads[ApiSys]
40 | implicit val apiCoordReads = Json.reads[ApiCoord]
41 | implicit val apiModelReads = Json.reads[ApiModel]
42 |
43 | }
44 |
45 | object JsonParser
46 | extends Parser[ApiModel]
47 | with ApiReads {
48 |
49 | override def parse(response: Plain, stream: InputStream): ApiModel =
50 | Json.parse(scala.io.Source.fromInputStream(stream).mkString).as[ApiModel]
51 |
52 | }
53 |
54 | trait ForecastServices
55 | extends NetUtils
56 | with Conversions {
57 |
58 | def loadJsonUrl(latitude: Double, longitude: Double)(implicit context: ContextWrapper): String =
59 | resGetString(R.string.openweather_url, latitude.toString, longitude.toString)
60 |
61 | def loadHeaderTuple(implicit context: ContextWrapper): (String, String) =
62 | (resGetString(R.string.openweather_key_name), resGetString(R.string.openweather_key_value))
63 |
64 | def loadForecast(request: ForecastRequest)(implicit context: ContextWrapper): Future[ForecastResponse] = {
65 | import scala.concurrent.ExecutionContext.Implicits.global
66 |
67 | implicit val parser = JsonParser
68 |
69 | val result = loadJson[ApiModel](loadJsonUrl(request.latitude, request.longitude), Seq(loadHeaderTuple))
70 | result.transform(
71 | response => ForecastResponse(Some(toForecast(response))),
72 | throwable => throwable)
73 | }
74 | }
75 |
76 | object ForecastServices extends ForecastServices
77 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/forecast/model/ApiModel.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.modules.forecast.model
2 |
3 | case class ApiModel(
4 | id: Long,
5 | dt: Long,
6 | base: String,
7 | cod: Int,
8 | name: String,
9 | coord: ApiCoord,
10 | sys: ApiSys,
11 | main: ApiMain,
12 | weather: Seq[ApiWeather],
13 | wind: ApiWind,
14 | rain: Option[Map[String, Double]],
15 | clouds: ApiClouds)
16 |
17 | case class ApiCoord(
18 | lon: Double,
19 | lat: Double)
20 |
21 | case class ApiSys(
22 | message: Option[Double],
23 | country: Option[String],
24 | sunrise: Option[Long],
25 | sunset: Option[Long])
26 |
27 | case class ApiMain(
28 | temp: Option[Double],
29 | temp_min: Option[Double],
30 | temp_max: Option[Double],
31 | pressure: Option[Double],
32 | sea_level: Option[Double],
33 | grnd_level: Option[Double],
34 | humidity: Option[Int])
35 |
36 | case class ApiWeather(
37 | id: Int,
38 | main: String,
39 | description: String,
40 | icon: String)
41 |
42 | case class ApiWind(
43 | speed: Option[Double],
44 | deg: Option[Double])
45 |
46 | case class ApiClouds(
47 | all: Option[Int])
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/forecast/model/Conversions.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.modules.forecast.model
2 |
3 | trait Conversions {
4 |
5 | def toForecast(apiModel: ApiModel) =
6 | Forecast(
7 | Location(apiModel.id, apiModel.name, apiModel.coord.lat, apiModel.coord.lon),
8 | apiModel.weather.headOption map (toWeather(_, apiModel.main.temp)))
9 |
10 | def toWeather(weather: ApiWeather, temperature: Option[Double]) =
11 | Weather(weather.id, weather.main, weather.description, weather.icon, temperature)
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/forecast/model/Forecast.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.modules.forecast.model
2 |
3 | case class Forecast(location: Location, weather: Option[Weather])
4 |
5 | case class Location(id: Long, name: String, latitude: Double, longitude: Double)
6 |
7 | case class Weather(id: Int, name: String, description: String, icon: String, temperature: Option[Double])
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/modules/utils/NetUtils.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.modules.utils
2 |
3 | import com.squareup.okhttp.{Headers, OkHttpClient}
4 | import io.taig.communicator.result.Parser
5 |
6 | import scala.concurrent.Future
7 |
8 | trait NetUtils {
9 |
10 | def loadJson[T](url: String, headers: Seq[(String, String)] = Seq.empty)(implicit parser: Parser[T], client: OkHttpClient = new OkHttpClient()): Future[T] = {
11 |
12 | import io.taig.communicator._
13 |
14 | import scala.concurrent.ExecutionContext.Implicits.global
15 |
16 | Request(url)
17 | .headers(toHeaders(headers))
18 | .get()
19 | .parse[T]
20 | .transform(response => response.payload, throwable => throwable)
21 | }
22 |
23 | def toHeaders(headers: Seq[(String, String)]): Headers = {
24 | val headersBuilder = new Headers.Builder()
25 | headers map { header =>
26 | headersBuilder.add(header._1, header._2)
27 | }
28 | headersBuilder.build()
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/AkkaSimonActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.FragmentActivity
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.MenuItem
7 | import com.fortysevendeg.macroid.extras.TextTweaks._
8 | import com.fortysevendeg.scala.android.R
9 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.{ColorActor, ComputerActor}
10 | import com.fortysevendeg.scala.android.ui.akkasimon.util.FragmentEnum._
11 | import macroid.Contexts
12 | import macroid.FullDsl._
13 | import macroid.akka.AkkaActivity
14 |
15 | class AkkaSimonActivity
16 | extends AppCompatActivity
17 | with Contexts[FragmentActivity]
18 | with AkkaActivity
19 | with Layout {
20 |
21 | val actorSystemName = "simonsystem"
22 |
23 | var roundCounter = 1
24 |
25 | lazy val computer = actorSystem.actorOf(ComputerActor.props, "computer")
26 | lazy val green = actorSystem.actorOf(ColorActor.props, GREEN.toLower)
27 | lazy val red = actorSystem.actorOf(ColorActor.props, RED.toLower)
28 | lazy val blue = actorSystem.actorOf(ColorActor.props, BLUE.toLower)
29 | lazy val yellow = actorSystem.actorOf(ColorActor.props, YELLOW.toLower)
30 |
31 | override def onCreate(savedInstanceState: Bundle) = {
32 | super.onCreate(savedInstanceState)
33 |
34 | (computer, green, red, blue, yellow)
35 |
36 | setContentView(layout)
37 |
38 | toolBar map setSupportActionBar
39 |
40 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
41 | }
42 |
43 | override def onDestroy(): Unit = {
44 | super.onDestroy()
45 | actorSystem.shutdown()
46 | }
47 |
48 | override def onOptionsItemSelected(item: MenuItem): Boolean = {
49 | item.getItemId match {
50 | case android.R.id.home => {
51 | finish()
52 | false
53 | }
54 | }
55 | super.onOptionsItemSelected(item)
56 | }
57 |
58 | def gameOver(rounds: Int) = goToOptions(rounds)
59 |
60 | def resetRound() = {
61 | roundCounter = 1
62 | rounds <~ tvText(getString(R.string.simon_round_counter, roundCounter.toString))
63 | }
64 |
65 | def nextRound() = {
66 | roundCounter += 1
67 | rounds <~ tvText(getString(R.string.simon_round_counter, roundCounter.toString))
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon
2 |
3 | import android.graphics.Color
4 | import android.support.v4.app.{Fragment, FragmentManager}
5 | import android.widget.{Button, FrameLayout, LinearLayout, TextView}
6 | import com.fortysevendeg.macroid.extras.FragmentExtras._
7 | import com.fortysevendeg.macroid.extras.TextTweaks._
8 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
9 | import com.fortysevendeg.macroid.extras.ViewTweaks._
10 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
11 | import com.fortysevendeg.scala.android.R
12 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor.NewGame
13 | import com.fortysevendeg.scala.android.ui.akkasimon.fragments.ColorFragment._
14 | import com.fortysevendeg.scala.android.ui.akkasimon.fragments.ComputerFragment._
15 | import com.fortysevendeg.scala.android.ui.akkasimon.fragments.{ColorFragment, ComputerFragment}
16 | import com.fortysevendeg.scala.android.ui.akkasimon.util.FragmentEnum._
17 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
18 | import macroid.FullDsl._
19 | import macroid._
20 |
21 | import scala.language.postfixOps
22 |
23 | trait Layout
24 | extends ToolbarLayout
25 | with IdGeneration
26 | with Styles {
27 |
28 | var btnStart = slot[Button]
29 |
30 | var optionsScreenLayout = slot[LinearLayout]
31 |
32 | var gameScreenLayout = slot[LinearLayout]
33 |
34 | var rounds = slot[TextView]
35 |
36 | var message = slot[TextView]
37 |
38 | def layout
39 | (implicit context: ActivityContextWrapper,
40 | managerContext: FragmentManagerContext[Fragment, FragmentManager]) = getUi(
41 | l[LinearLayout](
42 | toolBarLayout <~ tbTitle(R.string.simon_title),
43 | l[FrameLayout](
44 | f[ComputerFragment].
45 | pass(nameComputerKey -> COMPUTER.toLower).
46 | framed(Id.computer, COMPUTER.toLower),
47 | optionsScreen,
48 | gameScreen
49 | ) <~ contentStyle
50 | ) <~ rootStyle
51 | )
52 |
53 | def optionsScreen(implicit context: ActivityContextWrapper,
54 | managerContext: FragmentManagerContext[Fragment, FragmentManager]) = {
55 | l[LinearLayout](
56 | w[TextView] <~ wire(message) <~ messageStyle,
57 | w[Button] <~ buttonsStyle <~ On.click(Ui {
58 | findFragmentById[ComputerFragment](Id.computer) map (_.actor.get ! NewGame)
59 | } ~ goToGame())
60 | ) <~ optionsContentStyle <~ wire(optionsScreenLayout)
61 | }
62 |
63 | def gameScreen(implicit context: ActivityContextWrapper,
64 | managerContext: FragmentManagerContext[Fragment, FragmentManager]) = {
65 | l[LinearLayout](
66 | w[TextView] <~ roundsStyle <~ wire(rounds),
67 | l[LinearLayout](
68 | l[LinearLayout](
69 | f[ColorFragment]
70 | .pass(colorKey -> Color.GREEN, nameColorKey -> GREEN.toLower)
71 | .framed(Id.green, GREEN.toLower) <~ rowStyle,
72 | f[ColorFragment]
73 | .pass(colorKey -> Color.RED, nameColorKey -> RED.toLower)
74 | .framed(Id.red, RED.toLower) <~ rowStyle
75 | ) <~ columnStyle,
76 | l[LinearLayout](
77 | f[ColorFragment]
78 | .pass(colorKey -> Color.BLUE, nameColorKey -> BLUE.toLower)
79 | .framed(Id.blue, BLUE.toLower) <~ rowStyle,
80 | f[ColorFragment]
81 | .pass(colorKey -> Color.YELLOW, nameColorKey -> YELLOW.toLower)
82 | .framed(Id.yellow, YELLOW.toLower) <~ rowStyle
83 | ) <~ columnStyle
84 | ) <~ simonContainerStyle
85 | ) <~ gameContentStyle <~ wire(gameScreenLayout)
86 | }
87 |
88 | def goToGame() = (optionsScreenLayout <~ vGone) ~ (gameScreenLayout <~ vVisible)
89 |
90 | def goToOptions(rounds: Int)(implicit context: ContextWrapper) = {
91 | (optionsScreenLayout <~ vVisible) ~
92 | (gameScreenLayout <~ vGone) ~
93 | (message <~ tvText(resGetString(R.string.simon_rounds_message, rounds.toString)))
94 | }
95 |
96 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon
2 |
3 | import android.view.Gravity
4 | import android.widget.{TextView, Button, FrameLayout, LinearLayout}
5 | import com.fortysevendeg.macroid.extras.DeviceVersion.Lollipop
6 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
7 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
8 | import com.fortysevendeg.macroid.extras.TextTweaks._
9 | import com.fortysevendeg.macroid.extras.ViewTweaks._
10 | import com.fortysevendeg.scala.android.R
11 | import macroid.FullDsl._
12 | import macroid.{ContextWrapper, Tweak}
13 |
14 | import scala.language.postfixOps
15 |
16 | trait Styles {
17 |
18 | def simonButton(color: Int, alpha: Float = 0.3f)(implicit context: ContextWrapper) =
19 | vBackgroundColor(color) +
20 | vAlpha(alpha)
21 |
22 | val rootStyle: Tweak[LinearLayout] = llVertical
23 |
24 | val contentStyle: Tweak[FrameLayout] = llMatchWeightVertical
25 |
26 | val optionsContentStyle: Tweak[LinearLayout] =
27 | vMatchParent +
28 | llGravity(Gravity.CENTER) +
29 | llVertical
30 |
31 | def messageStyle(implicit context: ContextWrapper): Tweak[TextView] =
32 | vWrapContent +
33 | tvText(R.string.simon_welcome) +
34 | tvSizeResource(R.dimen.font_size_large) +
35 | tvAllCaps +
36 | tvGravity(Gravity.CENTER) +
37 | vPadding(paddingBottom = resGetDimensionPixelSize(R.dimen.padding_default_xlarge)) +
38 | tvNormalLight
39 |
40 | def buttonsStyle(implicit context: ContextWrapper): Tweak[Button] = {
41 | val size = resGetDimensionPixelSize(R.dimen.size_fab_default)
42 | lp[LinearLayout](size, size) +
43 | tvText(R.string.simon_start) +
44 | vBackground(R.drawable.background_default_fab) +
45 | (Lollipop ifSupportedThen vElevation(resGetDimension(R.dimen.padding_default_small)) getOrElse Tweak.blank)
46 | }
47 |
48 | val gameContentStyle: Tweak[LinearLayout] =
49 | vMatchParent +
50 | llVertical +
51 | vGone
52 |
53 | def roundsStyle(implicit context: ContextWrapper): Tweak[TextView] =
54 | vMatchWidth +
55 | tvGravity(Gravity.CENTER) +
56 | tvAllCaps +
57 | tvSizeResource(R.dimen.font_size_large) +
58 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
59 | tvNormalLight
60 |
61 | val simonContainerStyle: Tweak[LinearLayout] =
62 | llMatchWeightVertical +
63 | llHorizontal
64 |
65 | val columnStyle: Tweak[LinearLayout] =
66 | llMatchWeightHorizontal +
67 | llVertical
68 |
69 | val rowStyle: Tweak[FrameLayout] = llMatchWeightVertical
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/actors/ColorActor.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.actors
2 |
3 | import akka.actor.{ActorLogging, Props}
4 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor.RoundItemActorColor
5 | import com.fortysevendeg.scala.android.ui.akkasimon.fragments.ColorFragment
6 | import macroid.Ui
7 | import macroid.akka.FragmentActor
8 |
9 | class ColorActor extends FragmentActor[ColorFragment] with ActorLogging {
10 |
11 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ColorActor._
12 | import macroid.akka.FragmentActor._
13 |
14 | def receive = receiveUi andThen {
15 | case LightOn(Nil) =>
16 | withUi(f => f.receive)
17 | case LightOn(head :: tail) =>
18 | withUi(f => f.receive ~~ Ui(head.actor ! LightOn(tail)))
19 | case AttachUi(_) =>
20 | case DetachUi =>
21 | }
22 | }
23 |
24 | object ColorActor {
25 |
26 | case class LightOn(game: List[RoundItemActorColor])
27 |
28 | def props = Props(new ColorActor)
29 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/actors/ComputerActor.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.actors
2 |
3 | import akka.actor.{ActorLogging, ActorSelection, Props}
4 | import com.fortysevendeg.scala.android.ui.akkasimon.AkkaSimonActivity
5 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ColorActor.LightOn
6 | import com.fortysevendeg.scala.android.ui.akkasimon.fragments.ComputerFragment
7 | import macroid.akka.FragmentActor
8 |
9 | import scala.collection.mutable.ArrayBuffer
10 |
11 | class ComputerActor extends FragmentActor[ComputerFragment] with ActorLogging {
12 |
13 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor._
14 | import macroid.akka.FragmentActor._
15 |
16 | var gameList: List[RoundItemActorColor] = List.empty
17 |
18 | val receivedFromUser: ArrayBuffer[ClickedUserColor] = ArrayBuffer.empty
19 |
20 | def receive = receiveUi andThen {
21 | case NewGame =>
22 | withUi(f => f.newGame)
23 | case GameOver =>
24 | val rounds = gameList.length
25 | gameList = List.empty
26 | withUi(f => f.getActivity.asInstanceOf[AkkaSimonActivity].gameOver(rounds))
27 | case ResetRound(round) =>
28 | addRound(round)
29 | withUi(f => f.getActivity.asInstanceOf[AkkaSimonActivity].resetRound())
30 | case NextRound(round) =>
31 | addRound(round)
32 | withUi(f => f.getActivity.asInstanceOf[AkkaSimonActivity].nextRound())
33 | case userEvent: ClickedUserColor =>
34 | receivedFromUser += userEvent
35 | withUi(f => f.checkGame(gameList, receivedFromUser.toList map (_.color)))
36 | case AttachUi(_) =>
37 | case DetachUi =>
38 | }
39 |
40 | def addRound(round: RoundItemActorColor) = {
41 | gameList = gameList :+ round
42 | receivedFromUser.clear()
43 | gameList.head.actor ! LightOn(gameList.tail)
44 | }
45 |
46 | }
47 |
48 | object ComputerActor {
49 |
50 | case object NewGame
51 |
52 | case object GameOver
53 |
54 | case class ResetRound(round: RoundItemActorColor)
55 |
56 | case class NextRound(round: RoundItemActorColor)
57 |
58 | case class RoundItemActorColor(actor: ActorSelection, color: Int)
59 |
60 | case class ClickedUserColor(color: Int)
61 |
62 | def props = Props(new ComputerActor)
63 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/fragments/ColorFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.fragments
2 |
3 | import android.os.Bundle
4 | import android.view.{LayoutInflater, ViewGroup}
5 | import android.widget.Button
6 | import com.fortysevendeg.scala.android.ui.akkasimon.Styles
7 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor.ClickedUserColor
8 | import com.fortysevendeg.scala.android.ui.akkasimon.util.SimonAkkaFragment
9 | import com.fortysevendeg.macroid.extras.ViewTweaks._
10 | import macroid.FullDsl._
11 | import macroid._
12 | import ColorFragment._
13 |
14 | import scala.concurrent.ExecutionContext.Implicits.global
15 |
16 | class ColorFragment
17 | extends SimonAkkaFragment
18 | with Styles {
19 |
20 | lazy val actorName = getArguments.getString(nameColorKey)
21 | lazy val color = getArguments.getInt(colorKey)
22 |
23 | lazy val actor = Some(actorSystem.actorSelection(s"/user/$actorName"))
24 |
25 | var simonColor = slot[Button]
26 |
27 | def receive = lightColor(color)
28 |
29 | def lightColor(c: Int = color) = simonColor <~ vAlpha(1f) <~~ delay(600) <~ simonButton(c) <~~ delay(600)
30 |
31 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle) = getUi {
32 | w[Button] <~ wire(simonColor) <~ simonButton(color) <~ On.click(lightColor() ~~
33 | Ui(computerActor ! ClickedUserColor(color)))
34 | }
35 | }
36 |
37 | object ColorFragment {
38 | val nameColorKey = "name"
39 | val colorKey = "color"
40 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/fragments/ComputerFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.fragments
2 |
3 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor._
4 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor.RoundItemActorColor
5 | import com.fortysevendeg.scala.android.ui.akkasimon.util.SimonAkkaFragment
6 | import macroid._
7 | import ComputerFragment._
8 |
9 | class ComputerFragment
10 | extends SimonAkkaFragment {
11 |
12 | lazy val actorName = getArguments.getString(nameComputerKey)
13 |
14 | lazy val actor = Some(actorSystem.actorSelection(s"/user/$actorName"))
15 |
16 | def newGame = Ui(computerActor ! ResetRound(newRound()))
17 |
18 | def checkGame(gameList: List[RoundItemActorColor], userClicks: List[Int]) = Ui {
19 |
20 | if (userClicks.size == gameList.size) {
21 |
22 | val colors = gameList map (_.color)
23 |
24 | if (userClicks.zip(colors).exists(t => t._1 != t._2))
25 | computerActor ! GameOver
26 | else
27 | computerActor ! NextRound(newRound())
28 | }
29 | }
30 | }
31 |
32 | object ComputerFragment {
33 | val nameComputerKey = "name"
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/util/FragmentEnum.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.util
2 |
3 | object FragmentEnum {
4 | sealed trait FragmentEnumType
5 |
6 | case object GREEN extends FragmentEnumType
7 | case object RED extends FragmentEnumType
8 | case object BLUE extends FragmentEnumType
9 | case object YELLOW extends FragmentEnumType
10 |
11 | case object COMPUTER extends FragmentEnumType
12 |
13 | implicit class RichSimonColor(sc: FragmentEnumType) {
14 | def toLower = sc.toString.toLowerCase
15 | }
16 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/akkasimon/util/SimonAkkaFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.akkasimon.util
2 |
3 | import android.graphics.Color
4 | import com.fortysevendeg.scala.android.ui.akkasimon.actors.ComputerActor.RoundItemActorColor
5 | import com.fortysevendeg.scala.android.ui.akkasimon.util.FragmentEnum._
6 | import macroid.Contexts
7 | import macroid.akka.AkkaFragment
8 | import scala.language.postfixOps
9 | import scala.util.Random
10 |
11 | trait SimonAkkaFragment
12 | extends AkkaFragment
13 | with Contexts[AkkaFragment] {
14 |
15 | val random = Random
16 |
17 | def customActorPath(actorName: String) = s"/user/$actorName"
18 |
19 | lazy val computerActor = actorSystem.actorSelection(customActorPath(COMPUTER.toLower))
20 |
21 | lazy val greenActor = actorSystem.actorSelection(customActorPath(GREEN.toLower))
22 |
23 | lazy val redActor = actorSystem.actorSelection(customActorPath(RED.toLower))
24 |
25 | lazy val blueActor = actorSystem.actorSelection(customActorPath(BLUE.toLower))
26 |
27 | lazy val yellowActor = actorSystem.actorSelection(customActorPath(YELLOW.toLower))
28 |
29 | def newRound(): RoundItemActorColor = {
30 | val actorList = List(
31 | RoundItemActorColor(greenActor, Color.GREEN),
32 | RoundItemActorColor(redActor, Color.RED),
33 | RoundItemActorColor(blueActor, Color.BLUE),
34 | RoundItemActorColor(yellowActor, Color.YELLOW))
35 | actorList(random.nextInt(4))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/apirequest/ForecastApiRequestActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.apirequest
2 |
3 | import android.app.AlertDialog
4 | import android.content.Context
5 | import android.location.{Criteria, Location, LocationListener, LocationManager}
6 | import android.os.Bundle
7 | import android.support.v4.app.FragmentActivity
8 | import android.support.v7.app.AppCompatActivity
9 | import android.text.util.Linkify
10 | import android.view.{Menu, MenuItem}
11 | import com.fortysevendeg.macroid.extras.FragmentExtras._
12 | import com.fortysevendeg.scala.android.R
13 | import macroid.Contexts
14 | import macroid.FullDsl._
15 |
16 | class ForecastApiRequestActivity
17 | extends AppCompatActivity
18 | with Contexts[FragmentActivity]
19 | with Layout
20 | with DefaultLocationListener {
21 |
22 | lazy val locationManager = this.getSystemService(Context.LOCATION_SERVICE).asInstanceOf[LocationManager]
23 |
24 | override def onCreate(savedInstanceState: Bundle) = {
25 | super.onCreate(savedInstanceState)
26 |
27 | setContentView(layout)
28 |
29 | toolBar map setSupportActionBar
30 |
31 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
32 |
33 | if (savedInstanceState == null) {
34 | runUi(addFragment(f[ForecastFragment], Some(Id.fragment), Some(forecastFragmentName)))
35 | }
36 | }
37 |
38 | def loadClientLocation = {
39 | val criteria = new Criteria()
40 | criteria.setAccuracy(Criteria.NO_REQUIREMENT)
41 |
42 | Option(locationManager.getBestProvider(criteria, true)) map { provider =>
43 | val last = Option(locationManager.getLastKnownLocation(provider))
44 | if (last.isDefined) loadForecast(last.get.getLatitude, last.get.getLongitude)
45 | else locationManager.requestLocationUpdates(provider, 0, 0, this)
46 | }
47 | }
48 |
49 | override def onCreateOptionsMenu(menu: Menu): Boolean = {
50 | getMenuInflater.inflate(R.menu.activity_forecast, menu)
51 | true
52 | }
53 |
54 | override def onOptionsItemSelected(item: MenuItem): Boolean =
55 | item.getItemId match {
56 | case R.id.about_icons =>
57 | val dialogView = new AboutDialogLayout
58 | dialogView.textView map (Linkify.addLinks(_, Linkify.WEB_URLS))
59 | val dialogBuilder = new AlertDialog.Builder(this)
60 | dialogBuilder.setView(dialogView.content)
61 | dialogBuilder.setPositiveButton(android.R.string.ok, null)
62 | dialogBuilder.show()
63 | true
64 | case android.R.id.home =>
65 | finish()
66 | true
67 | case _ => super.onOptionsItemSelected(item)
68 | }
69 |
70 | override def onLocationChanged(location: Location) = {
71 | locationManager.removeUpdates(this)
72 | loadForecast(location.getLatitude, location.getLongitude)
73 | }
74 |
75 | override def onProviderDisabled(provider: String) = {
76 | locationManager.removeUpdates(this)
77 | showError(R.string.error_message_api_request_location_api)
78 | }
79 |
80 | private def showError(errorMessage: Int) =
81 | findFragmentByTag[ForecastFragment](forecastFragmentName) map (_.error(Some(errorMessage)))
82 |
83 | private def loadForecast(latitude: Double, longitude: Double) =
84 | findFragmentByTag[ForecastFragment](forecastFragmentName) map (_.loadForecast((latitude, longitude)))
85 | }
86 |
87 | trait DefaultLocationListener extends LocationListener {
88 |
89 | override def onLocationChanged(location: Location) = {}
90 |
91 | override def onProviderEnabled(provider: String) = {}
92 |
93 | override def onStatusChanged(provider: String, status: Int, extras: Bundle) = {}
94 |
95 | override def onProviderDisabled(provider: String) = {}
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/apirequest/ForecastFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.apirequest
2 |
3 | import java.text.DecimalFormat
4 |
5 | import android.graphics.drawable.Drawable
6 | import android.os.Bundle
7 | import android.support.v4.app.Fragment
8 | import android.view.{LayoutInflater, View, ViewGroup}
9 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
10 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
11 | import com.fortysevendeg.macroid.extras.TextTweaks._
12 | import com.fortysevendeg.macroid.extras.ViewTweaks._
13 | import com.fortysevendeg.scala.android.R
14 | import com.fortysevendeg.scala.android.modules.forecast.ForecastRequest
15 | import com.fortysevendeg.scala.android.modules.forecast.impl.ForecastServices
16 | import com.fortysevendeg.scala.android.modules.forecast.model.{Weather, Forecast}
17 | import macroid.FullDsl._
18 | import macroid.{ContextWrapper, Contexts, Ui}
19 |
20 | import scala.concurrent.ExecutionContext.Implicits.global
21 |
22 | class ForecastFragment
23 | extends Fragment
24 | with Contexts[Fragment] {
25 |
26 | private var fragmentLayout: Option[ForecastFragmentLayout] = None
27 |
28 | val decimalFormatter = new DecimalFormat("#.##'°'")
29 |
30 | val resourceName = "forecast_%s"
31 |
32 | val placeholderTemperature = "-°"
33 |
34 | val iconSizeIdentifier = 2
35 |
36 | val sampleLocationProvider = "fake_provider"
37 |
38 | val sampleLocationLatitude = 47.6632164
39 |
40 | val sampleLocationLongitude = -122.3842024
41 |
42 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = {
43 |
44 | val fLayout = new ForecastFragmentLayout
45 |
46 | fragmentLayout = Some(fLayout)
47 |
48 | runUi(
49 | (fLayout.reloadButton <~ On.click(Ui { reload })) ~
50 | (fLayout.progressButton <~ On.click(Ui { loadSampleLocation }))
51 | )
52 |
53 | fLayout.layout
54 | }
55 |
56 | override def onViewCreated(view: View, savedInstanceState: Bundle) = {
57 | super.onViewCreated(view, savedInstanceState)
58 | reload
59 | }
60 |
61 | def reload = {
62 | loadingForLocation
63 | Option(getActivity) map (_.asInstanceOf[ForecastApiRequestActivity].loadClientLocation)
64 | }
65 |
66 | def loadSampleLocation = {
67 | val location = new android.location.Location(sampleLocationProvider)
68 | location.setLatitude(sampleLocationLatitude)
69 | location.setLongitude(sampleLocationLongitude)
70 | Option(getActivity) map (_.asInstanceOf[ForecastApiRequestActivity].onLocationChanged(location))
71 | }
72 |
73 | def loadForecast(location: (Double, Double)) = {
74 | loadingForForecast
75 |
76 | val result = for {
77 | forecast <- ForecastServices.loadForecast(ForecastRequest(location._1, location._2))
78 | } yield forecast.forecastMaybe
79 |
80 | result map {
81 | case Some(forecast) => showForecast(forecast)
82 | case _ => error(Some(R.string.error_message_api_request_loading))
83 | } recover {
84 | case _ => error(Some(R.string.error_message_api_request_loading))
85 | }
86 | }
87 |
88 | def showForecast(forecast: Forecast) =
89 | fragmentLayout map { layout =>
90 | runUi(
91 | (layout.progressContent <~ vGone) ~
92 | (layout.errorContent <~ vGone) ~
93 | (layout.detailLayoutContent <~ vVisible) ~
94 | (layout.locationTextView <~ tvText(forecast.location.name)) ~
95 | (layout.forecastImageView <~ ivSrc(loadWeatherIcon(forecast.weather))) ~
96 | (layout.temperatureTextView <~ tvText(loadWeatherTemperature(forecast.weather)))
97 | )
98 | }
99 |
100 | def loadWeatherIcon(weatherMaybe: Option[Weather]): Drawable = {
101 | val result = weatherMaybe flatMap { weather =>
102 | Option(weather.icon) match {
103 | case Some(icon) if icon.length >= iconSizeIdentifier => resGetDrawable(String.format(resourceName, icon.substring(0, iconSizeIdentifier)))
104 | case _ => Option(resGetDrawable(R.drawable.unknown))
105 | }
106 | }
107 | result getOrElse resGetDrawable(R.drawable.unknown)
108 | }
109 |
110 | def loadWeatherTemperature(weatherMaybe: Option[Weather]): String = {
111 | (for {
112 | weather <- weatherMaybe
113 | temperature <- weather.temperature
114 | } yield decimalFormatter.format(temperature)) getOrElse placeholderTemperature
115 | }
116 |
117 | def loadingForForecast =
118 | fragmentLayout map { layout =>
119 | runUi(
120 | (layout.progressContent <~ vVisible) ~
121 | (layout.progressText <~ tvText(R.string.loading_forecast_message)) ~
122 | (layout.progressButton <~ vGone) ~
123 | (layout.errorContent <~ vGone) ~
124 | (layout.detailLayoutContent <~ vGone)
125 | )
126 | }
127 |
128 | def loadingForLocation =
129 | fragmentLayout map { layout =>
130 | runUi(
131 | (layout.progressContent <~ vVisible) ~
132 | (layout.progressText <~ tvText(R.string.loading_location_message)) ~
133 | (layout.progressButton <~ tvText(R.string.try_sample_location_button) <~ vVisible) ~
134 | (layout.errorContent <~ vGone) ~
135 | (layout.detailLayoutContent <~ vGone)
136 | )
137 | }
138 |
139 | def error(errorMessage: Option[Int]) =
140 | fragmentLayout map { layout =>
141 | runUi(
142 | (layout.progressContent <~ vGone) ~
143 | (layout.errorContent <~ vVisible) ~
144 | (layout.errorText <~ tvText(errorMessage getOrElse R.string.error_message_api_request_default)) ~
145 | (layout.detailLayoutContent <~ vGone)
146 | )
147 | }
148 |
149 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/apirequest/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.apirequest
2 |
3 | import android.support.v4.app.{Fragment, FragmentManager}
4 | import android.widget._
5 | import com.fortysevendeg.macroid.extras.TextTweaks._
6 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
7 | import com.fortysevendeg.scala.android.R
8 | import com.fortysevendeg.scala.android.ui.apirequest.Styles._
9 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
10 | import macroid.FullDsl._
11 | import macroid.{ActivityContextWrapper, ContextWrapper, FragmentManagerContext, IdGeneration}
12 |
13 | trait Layout extends ToolbarLayout with IdGeneration {
14 |
15 | val loaderFragmentName = Tag.fragmentLoader
16 |
17 | val forecastFragmentName = Tag.fragmentForecast
18 |
19 | var content = slot[FrameLayout]
20 |
21 | def layout(implicit context: ActivityContextWrapper,
22 | managerContext: FragmentManagerContext[Fragment, FragmentManager]) = getUi(
23 | l[LinearLayout](
24 | toolBarLayout <~ tbTitle(R.string.title_forecast_api_request),
25 | l[FrameLayout]() <~ wire(content) <~ id(Id.fragment) <~ contentStyle
26 | ) <~ rootStyle
27 | )
28 |
29 | }
30 |
31 | trait ErrorLayout {
32 |
33 | var progressContent = slot[LinearLayout]
34 |
35 | var progressBar = slot[ProgressBar]
36 |
37 | var progressText = slot[TextView]
38 |
39 | var progressButton = slot[Button]
40 |
41 | var errorContent = slot[LinearLayout]
42 |
43 | var reloadButton = slot[Button]
44 |
45 | var errorText = slot[TextView]
46 |
47 | def layoutView(implicit context: ActivityContextWrapper) =
48 | l[FrameLayout](
49 | l[LinearLayout](
50 | w[ProgressBar] <~ wire(progressBar) <~ progressBarStyle,
51 | w[TextView] <~ wire(progressText) <~ messageStyle,
52 | w[Button] <~ wire(progressButton) <~ buttonStyle
53 | ) <~ wire(progressContent) <~ progressContentStyle,
54 | l[LinearLayout](
55 | w[TextView] <~ wire(errorText) <~ messageStyle,
56 | w[Button] <~ wire(reloadButton) <~ tvText(R.string.try_again_button) <~ buttonStyle
57 | ) <~ wire(errorContent) <~ errorContentStyle
58 | ) <~ errorLayoutStyle
59 |
60 | }
61 |
62 | class ForecastFragmentLayout(implicit context: ActivityContextWrapper) extends ErrorLayout {
63 |
64 | var errorLayoutContent = slot[FrameLayout]
65 |
66 | var detailLayoutContent = slot[LinearLayout]
67 |
68 | var locationTextView = slot[TextView]
69 |
70 | var forecastImageView = slot[ImageView]
71 |
72 | var temperatureTextView = slot[TextView]
73 |
74 | val content = getUi(
75 | l[FrameLayout](
76 | layoutView <~ wire(errorLayoutContent),
77 | l[LinearLayout](
78 | l[LinearLayout](
79 | w[ImageView] <~ markerImageViewStyle,
80 | w[TextView] <~ wire(locationTextView) <~ locationTextViewStyle
81 | ) <~ forecastLocationLayoutStyle,
82 | l[LinearLayout](
83 | w[ImageView] <~ wire(forecastImageView) <~ forecastImageViewStyle,
84 | w[TextView] <~ wire(temperatureTextView) <~ temperatureTextViewStyle
85 | ) <~ forecastDetailLayoutStyle
86 | ) <~ wire(detailLayoutContent) <~ forecastLayoutStyle
87 | ) <~ forecastFragmentLayoutStyle
88 | )
89 |
90 | def layout = content
91 |
92 | }
93 |
94 | class AboutDialogLayout(implicit context: ActivityContextWrapper) {
95 |
96 | var textView = slot[TextView]
97 |
98 | val content = getUi(
99 | l[FrameLayout](
100 | w[TextView] <~ wire(textView) <~ dialogTextViewStyle
101 | ) <~ aboutDialogLayoutStyle
102 | )
103 |
104 | def layout = content
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/apirequest/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.apirequest
2 |
3 | import android.view.Gravity
4 | import android.widget.{ImageView, ProgressBar, LinearLayout, TextView}
5 | import com.fortysevendeg.macroid.extras.FrameLayoutTweaks._
6 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
7 | import com.fortysevendeg.macroid.extras.TextTweaks._
8 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
9 | import com.fortysevendeg.macroid.extras.ViewTweaks._
10 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
11 | import com.fortysevendeg.scala.android.R
12 | import macroid.{ContextWrapper, Tweak}
13 |
14 | import scala.language.postfixOps
15 |
16 | object Styles {
17 |
18 | val rootStyle = llVertical
19 |
20 | val contentStyle = vMatchParent
21 |
22 | val errorLayoutStyle = vMatchParent
23 |
24 | val errorContentStyle: Tweak[LinearLayout] =
25 | vWrapContent +
26 | flLayoutGravity(Gravity.CENTER) +
27 | llGravity(Gravity.CENTER_HORIZONTAL) +
28 | llVertical +
29 | vGone
30 |
31 | val progressContentStyle: Tweak[LinearLayout] =
32 | vWrapContent +
33 | flLayoutGravity(Gravity.CENTER) +
34 | llGravity(Gravity.CENTER_HORIZONTAL) +
35 | llVertical +
36 | vGone
37 |
38 | val progressBarStyle: Tweak[ProgressBar] =
39 | vWrapContent +
40 | flLayoutGravity(Gravity.CENTER)
41 |
42 | def messageStyle(implicit context: ContextWrapper): Tweak[TextView] =
43 | vWrapContent +
44 | tvGravity(Gravity.CENTER) +
45 | tvColorResource(R.color.text_error_message) +
46 | tvSizeResource(R.dimen.text_size_forecast_error_message) +
47 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
48 |
49 | def buttonStyle(implicit context: ContextWrapper): Tweak[TextView] =
50 | vWrapContent +
51 | vMinWidth(resGetDimensionPixelSize(R.dimen.width_forecast_error_button)) +
52 | tvColorResource(R.color.text_error_button) +
53 | vBackground(R.drawable.background_error_button) +
54 | tvAllCaps +
55 | tvSizeResource(R.dimen.text_size_forecast_error_button) +
56 | tvGravity(Gravity.CENTER)
57 |
58 | val forecastFragmentLayoutStyle = vMatchParent
59 |
60 | def forecastLayoutStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
61 | vMatchParent +
62 | llVertical +
63 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
64 |
65 | def forecastLocationLayoutStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
66 | vMatchWidth +
67 | llHorizontal +
68 | llGravity(Gravity.CENTER_VERTICAL)
69 |
70 | def forecastDetailLayoutStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
71 | vMatchParent +
72 | llVertical +
73 | llGravity(Gravity.CENTER)
74 |
75 | def markerImageViewStyle(implicit context: ContextWrapper): Tweak[ImageView] =
76 | vWrapContent +
77 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
78 | ivSrc(R.drawable.map_marker)
79 |
80 | def locationTextViewStyle(implicit context: ContextWrapper) =
81 | vWrapContent +
82 | tvSizeResource(R.dimen.text_size_forecast_location) +
83 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
84 |
85 | val forecastImageViewStyle = vWrapContent
86 |
87 | def temperatureTextViewStyle(implicit context: ContextWrapper) =
88 | vWrapContent +
89 | tvGravity(Gravity.CENTER) +
90 | tvSizeResource(R.dimen.text_size_forecast_temperature) +
91 | tvBoldCondensed +
92 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
93 |
94 | def aboutDialogLayoutStyle(implicit context: ContextWrapper) =
95 | vMatchParent +
96 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
97 |
98 | val dialogTextViewStyle =
99 | vWrapContent +
100 | tvText(R.string.forecast_attribution)
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/circularreveal/CircularRevealActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.circularreveal
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.{Fragment, FragmentActivity}
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.MenuItem
7 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
8 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawableTweaks._
9 | import macroid.{Ui, Contexts}
10 | import com.fortysevendeg.macroid.extras.FragmentExtras._
11 | import macroid.FullDsl._
12 |
13 | class CircularRevealActivity
14 | extends AppCompatActivity
15 | with Contexts[FragmentActivity]
16 | with Layout {
17 |
18 | override def onCreate(savedInstanceState: Bundle) = {
19 | super.onCreate(savedInstanceState)
20 |
21 | setContentView(layout)
22 |
23 | toolBar foreach setSupportActionBar
24 |
25 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
26 |
27 | }
28 |
29 | def remove(fragment: Fragment): Unit = removeFragment(fragment)
30 |
31 | override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
32 | case android.R.id.home =>
33 | finish()
34 | false
35 | case _ => super.onOptionsItemSelected(item)
36 | }
37 |
38 | override def onBackPressed(): Unit =
39 | findFragmentByTag[SampleFragment](fragmentName) match {
40 | case Some(f) =>
41 | runUi(Ui(f.unreveal()) ~
42 | (circleButton <~ pmdAnimIcon(ADD)))
43 | case _ => super.onBackPressed()
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/circularreveal/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.circularreveal
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.{FragmentManager, Fragment}
5 | import android.support.v7.widget.CardView
6 | import android.widget.{ImageView, TextView, FrameLayout, LinearLayout}
7 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
8 | import com.fortysevendeg.scala.android.R
9 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
10 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
11 | import macroid.FullDsl._
12 | import macroid._
13 | import com.fortysevendeg.macroid.extras.FragmentExtras._
14 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawableTweaks._
15 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
16 |
17 | trait Layout
18 | extends ToolbarLayout
19 | with IdGeneration
20 | with Styles {
21 |
22 | val fragmentName = "sample-fragment"
23 |
24 | var circleButton = slot[ImageView]
25 |
26 | var content = slot[FrameLayout]
27 |
28 | def layout(implicit context: ActivityContextWrapper,
29 | managerContext: FragmentManagerContext[Fragment, FragmentManager]) = getUi(
30 | l[LinearLayout](
31 | toolBarLayout <~ tbTitle(R.string.title_circular_reveal_styles),
32 | l[FrameLayout](
33 | l[FrameLayout]() <~ wire(content) <~ id(Id.fragment) <~ fragmentStyle,
34 | w[ImageView] <~ wire(circleButton) <~ fabStyle <~ On.Click {
35 | findFragmentByTag[SampleFragment](fragmentName) match {
36 | case Some(f) =>
37 | Ui(f.unreveal()) ~
38 | (circleButton <~ pmdAnimIcon(ADD))
39 | case _ =>
40 | val margin = resGetDimensionPixelSize(R.dimen.padding_default)
41 | val (x: Int, y: Int) = (for {
42 | circle <- circleButton
43 | c <- content
44 | } yield (circle.getLeft - margin - c.getLeft + circle.getWidth / 2,
45 | circle.getTop - margin - c.getTop + (circle.getHeight / 2))).getOrElse(0, 0)
46 | val args = new Bundle()
47 | args.putInt(SampleFragment.posX, x)
48 | args.putInt(SampleFragment.posY, y)
49 | addFragment(f[SampleFragment].pass(args), Some(Id.fragment), Some(fragmentName)) ~
50 | (circleButton <~ pmdAnimIcon(CLOSE))
51 | }
52 | }
53 | ) <~ contentStyle
54 | ) <~ rootStyle
55 | )
56 |
57 | }
58 |
59 | class FragmentLayout(implicit context: ActivityContextWrapper)
60 | extends FragmentStyles {
61 |
62 | val content = getUi(
63 | l[CardView](
64 | l[LinearLayout](
65 | w[ImageView] <~ imageStyle,
66 | w[TextView] <~ textTitleStyle,
67 | w[TextView] <~ textMessageStyle
68 | ) <~ contentLayoutStyle
69 | ) <~ contentRevealStyle
70 | )
71 |
72 | def layout = content
73 |
74 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/circularreveal/SampleFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.circularreveal
2 |
3 | import android.animation.{AnimatorListenerAdapter, ObjectAnimator, Animator}
4 | import android.annotation.TargetApi
5 | import android.os.Build.VERSION
6 | import android.os.{Build, Bundle}
7 | import android.support.v4.app.Fragment
8 | import android.view.animation.{AccelerateInterpolator, DecelerateInterpolator}
9 | import android.view.{ViewAnimationUtils, View, ViewGroup, LayoutInflater}
10 | import com.fortysevendeg.macroid.extras.SnailsUtils
11 | import com.fortysevendeg.macroid.extras.DeviceVersion._
12 | import macroid.Contexts
13 |
14 | class SampleFragment
15 | extends Fragment
16 | with Contexts[Fragment] {
17 |
18 | private var lastWidth: Option[Int] = None
19 |
20 | private var lastHeight: Option[Int] = None
21 |
22 | private var lastRevealX: Option[Int] = None
23 |
24 | private var lastRevealY: Option[Int] = None
25 |
26 | private var fragmentLayout: Option[FragmentLayout] = None
27 |
28 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = {
29 |
30 | val fLayout = new FragmentLayout
31 |
32 | fragmentLayout = Some(fLayout)
33 |
34 | fLayout.layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
35 | override def onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int): Unit = {
36 | v.removeOnLayoutChangeListener(this)
37 | reveal(right - left, bottom - top)
38 | }
39 | })
40 |
41 | fLayout.layout
42 |
43 | }
44 |
45 | def reveal(width: Int, height: Int) = {
46 | lastWidth = Some(width)
47 | lastHeight = Some(height)
48 | fragmentLayout.map {
49 | fLayout =>
50 | CurrentVersion match {
51 | case version if version >= Lollipop =>
52 | getLollipopReveal(fLayout.layout, width, height)
53 | case _ =>
54 | ltLollipopReveal(fLayout.layout)
55 | }
56 | }
57 | }
58 |
59 | def unreveal(): Unit = {
60 | fragmentLayout.map {
61 | fLayout =>
62 | CurrentVersion match {
63 | case version if version >= Lollipop =>
64 | getLollipopUnreveal(fLayout.layout)
65 | case _ =>
66 | ltLollipopUnreveal(fLayout.layout)
67 | }
68 | }
69 | }
70 |
71 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
72 | def getLollipopReveal(layout: View, width: Int, height: Int) = {
73 | val cx = getArguments.getInt(SampleFragment.posX, 0)
74 | val cy = getArguments.getInt(SampleFragment.posY, 0)
75 | lastRevealX = Some(cx)
76 | lastRevealY = Some(cy)
77 | val endRadius = SnailsUtils.calculateRadius(cx, cy, width, height)
78 | val reveal: Animator = ViewAnimationUtils.createCircularReveal(layout, cx, cy, 0, endRadius)
79 | reveal.setInterpolator(new DecelerateInterpolator(2f))
80 | reveal.start()
81 | }
82 |
83 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
84 | def getLollipopUnreveal(layout: View) = {
85 | val (x, y, w, h) = (for {
86 | x <- lastRevealX
87 | y <- lastRevealY
88 | w <- lastWidth
89 | h <- lastHeight
90 | } yield (x, y, w, h)) getOrElse(0, 0, 0, 0)
91 | val radius = SnailsUtils.calculateRadius(x, y, w, h)
92 | val reveal: Animator = ViewAnimationUtils.createCircularReveal(layout, x, y, radius, 0)
93 | reveal.setInterpolator(new DecelerateInterpolator(2f))
94 | reveal.addListener(new AnimatorListenerAdapter {
95 | override def onAnimationEnd(animation: Animator): Unit = {
96 | super.onAnimationEnd(animation)
97 | layout.setVisibility(View.GONE)
98 | getActivity.asInstanceOf[CircularRevealActivity].remove(SampleFragment.this)
99 | }
100 | })
101 | reveal.start()
102 | }
103 |
104 | def ltLollipopReveal(layout: View) = {
105 | val alpha: Animator = ObjectAnimator.ofFloat(layout, "alpha", 0f, 1)
106 | alpha.setInterpolator(new DecelerateInterpolator(2f))
107 | alpha.start()
108 | }
109 |
110 | def ltLollipopUnreveal(layout: View) = {
111 | val alpha: Animator = ObjectAnimator.ofFloat(layout, "alpha", 1f, 0f)
112 | alpha.setInterpolator(new AccelerateInterpolator(2f))
113 | alpha.addListener(new AnimatorListenerAdapter {
114 | override def onAnimationEnd(animation: Animator): Unit = {
115 | super.onAnimationEnd(animation)
116 | layout.setVisibility(View.GONE)
117 | getActivity.asInstanceOf[CircularRevealActivity].remove(SampleFragment.this)
118 | }
119 | })
120 | alpha.start()
121 | }
122 |
123 | }
124 |
125 | object SampleFragment {
126 |
127 | val posX = "pos_x"
128 | val posY = "pos_y"
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/circularreveal/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.circularreveal
2 |
3 | import android.graphics.Color
4 | import android.support.v7.widget.CardView
5 | import android.view.Gravity
6 | import android.view.ViewGroup.LayoutParams._
7 | import android.widget.ImageView.ScaleType
8 | import android.widget.{FrameLayout, ImageView, LinearLayout, TextView}
9 | import com.fortysevendeg.macroid.extras.DeviceVersion._
10 | import com.fortysevendeg.macroid.extras.FrameLayoutTweaks._
11 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
12 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
13 | import com.fortysevendeg.macroid.extras.TextTweaks._
14 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
15 | import com.fortysevendeg.macroid.extras.ViewTweaks._
16 | import com.fortysevendeg.scala.android.R
17 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
18 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawable
19 | import macroid.FullDsl._
20 | import macroid.{ActivityContextWrapper, ContextWrapper, Tweak}
21 | import com.fortysevendeg.scala.android.ui.commons.AsyncImageTweaks._
22 |
23 | import scala.language.postfixOps
24 |
25 | trait Styles {
26 |
27 | val rootStyle = llVertical
28 |
29 | def contentStyle(implicit context: ContextWrapper): Tweak[FrameLayout] =
30 | vMatchParent +
31 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
32 |
33 | def fragmentStyle(implicit context: ContextWrapper): Tweak[FrameLayout] = vMatchParent
34 |
35 | def fabStyle(implicit context: ContextWrapper): Tweak[ImageView] = {
36 | val size = resGetDimensionPixelSize(R.dimen.size_fab_default)
37 | lp[FrameLayout](size, size) +
38 | flLayoutGravity(Gravity.RIGHT | Gravity.BOTTOM) +
39 | vBackground(R.drawable.background_default_fab) +
40 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default_xlarge)) +
41 | ivSrc(new PathMorphDrawable(
42 | defaultIcon = ADD,
43 | defaultStroke = resGetDimensionPixelSize(R.dimen.circular_reveal_fab_stroke),
44 | defaultColor = Color.WHITE
45 | )) +
46 | vMargins(resGetDimensionPixelSize(R.dimen.padding_default)) +
47 | (Lollipop ifSupportedThen vElevation(resGetDimension(R.dimen.padding_default_small)) getOrElse Tweak.blank)
48 | }
49 |
50 | }
51 |
52 | trait FragmentStyles {
53 |
54 | def contentRevealStyle(implicit context: ContextWrapper): Tweak[CardView] =
55 | vMatchParent
56 |
57 | val contentLayoutStyle: Tweak[LinearLayout] =
58 | vMatchParent +
59 | llVertical
60 |
61 | def imageStyle(implicit context: ActivityContextWrapper): Tweak[ImageView] =
62 | lp[LinearLayout](MATCH_PARENT, resGetDimensionPixelSize(R.dimen.circular_reveal_height_image)) +
63 | srcImage(resGetString(R.string.material_list_url_image)) +
64 | ivScaleType(ScaleType.CENTER_CROP)
65 |
66 | def textTitleStyle(implicit context: ContextWrapper): Tweak[TextView] =
67 | tvText(R.string.circular_reveal_title) +
68 | tvSizeResource(R.dimen.font_size_large) +
69 | tvText(R.string.circular_reveal_title) +
70 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
71 |
72 | def textMessageStyle(implicit context: ContextWrapper): Tweak[TextView] =
73 | tvText(R.string.circular_reveal_title) +
74 | tvSizeResource(R.dimen.font_size_normal) +
75 | tvNormalLight +
76 | tvText(R.string.lorem_ipsum_large) +
77 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/commons/CommonsStyles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.commons
2 |
3 | import com.fortysevendeg.scala.android.R
4 | import com.fortysevendeg.macroid.extras.ViewTweaks._
5 |
6 | object CommonsStyles {
7 |
8 | val toolbarStyle =
9 | vBackground(R.color.primary) +
10 | vMatchWidth
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/commons/JsonReads.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.commons
2 |
3 | import com.fortysevendeg.scala.android.ui.main.{UserInfo, ProjectActivityInfo}
4 | import play.api.libs.json.Json
5 |
6 | trait JsonReads {
7 | implicit val userInfoReads = Json.reads[UserInfo]
8 | implicit val projectActivityInfoReads = Json.reads[ProjectActivityInfo]
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/commons/ScalaExtraTweaks.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | package com.fortysevendeg.scala.android.ui.commons
18 |
19 | import android.widget.ImageView
20 | import com.fortysevendeg.macroid.extras.DeviceVersion._
21 | import com.fortysevendeg.macroid.extras.ViewTweaks._
22 | import com.fortysevendeg.scala.android.ui.components.CircularTransformation
23 | import com.squareup.picasso.Picasso
24 | import macroid.{ActivityContextWrapper, Tweak}
25 |
26 | import scala.language.postfixOps
27 |
28 | object AsyncImageTweaks {
29 | type W = ImageView
30 |
31 | def roundedImage(url: String,
32 | placeHolder: Int,
33 | size: Int)(implicit context: ActivityContextWrapper) = CurrentVersion match {
34 | case sdk if sdk >= Lollipop =>
35 | srcImage(url, placeHolder) + vCircleOutlineProvider(0)
36 | case _ =>
37 | roundedImageTweak(url, placeHolder, size)
38 | }
39 |
40 | private def roundedImageTweak(
41 | url: String,
42 | placeHolder: Int,
43 | size: Int
44 | )(implicit context: ActivityContextWrapper): Tweak[W] = Tweak[W](
45 | imageView => {
46 | Picasso.`with`(context.getOriginal)
47 | .load(url)
48 | .transform(new CircularTransformation(size))
49 | .placeholder(placeHolder)
50 | .into(imageView)
51 | }
52 | )
53 |
54 | def srcImage(
55 | url: String,
56 | placeHolder: Int
57 | )(implicit context: ActivityContextWrapper): Tweak[W] = Tweak[W](
58 | imageView => {
59 | Picasso.`with`(context.getOriginal)
60 | .load(url)
61 | .placeholder(placeHolder)
62 | .into(imageView)
63 | }
64 | )
65 |
66 | def srcImage(url: String)(implicit context: ActivityContextWrapper): Tweak[W] = Tweak[W](
67 | imageView => {
68 | Picasso.`with`(context.getOriginal)
69 | .load(url)
70 | .into(imageView)
71 | }
72 | )
73 | }
74 |
75 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/commons/ToolbarLayout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.commons
2 |
3 | import android.support.v7.widget.Toolbar
4 | import android.view.ContextThemeWrapper
5 | import com.fortysevendeg.scala.android.R
6 | import com.fortysevendeg.scala.android.ui.commons.CommonsStyles._
7 | import macroid.FullDsl._
8 | import macroid.{ActivityContextWrapper, Ui}
9 |
10 | trait ToolbarLayout {
11 |
12 | var toolBar = slot[Toolbar]
13 |
14 | def toolBarLayout(implicit context: ActivityContextWrapper): Ui[Toolbar] =
15 | Ui {
16 | val darkToolBar = getToolbarThemeDarkActionBar
17 | toolBar = Some(darkToolBar)
18 | darkToolBar
19 | } <~ toolbarStyle
20 |
21 | private def getToolbarThemeDarkActionBar(implicit context: ActivityContextWrapper) = {
22 | val contextTheme = new ContextThemeWrapper(context.getOriginal, R.style.ThemeOverlay_AppCompat_Dark_ActionBar)
23 | val darkToolBar = new Toolbar(contextTheme)
24 | darkToolBar.setPopupTheme(R.style.ThemeOverlay_AppCompat_Light)
25 | darkToolBar
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/components/CircleView.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.components
2 |
3 | import android.content.Context
4 | import android.graphics.Paint.Style
5 | import android.graphics.{Canvas, Paint}
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import macroid.Tweak
9 |
10 | class CircleView(context: Context, attr: AttributeSet, defStyleAttr: Int)
11 | extends View(context, attr, defStyleAttr) {
12 |
13 | def this(context: Context) = this(context, null, 0)
14 |
15 | def this(context: Context, attr: AttributeSet) = this(context, attr, 0)
16 |
17 | private val paint = {
18 | val paint : Paint = new Paint()
19 | paint.setStyle(Style.FILL)
20 | paint
21 | }
22 |
23 | def setColor(color: Int) = {
24 | paint.setColor(color)
25 | }
26 |
27 | override def onDraw(canvas: Canvas): Unit = {
28 | super.onDraw(canvas)
29 | canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, paint)
30 | }
31 |
32 | }
33 |
34 | object CircleView {
35 | type W = CircleView
36 |
37 | def cvColor(color: Int) = Tweak[W] (_.setColor(color))
38 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/components/CircularTransformation.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | package com.fortysevendeg.scala.android.ui.components
18 |
19 | import android.graphics.{Bitmap, Canvas, Paint, PorterDuff, PorterDuffXfermode, Rect}
20 | import com.squareup.picasso.Transformation
21 |
22 | class CircularTransformation(size: Int) extends Transformation {
23 |
24 | val radius = Math.ceil(size / 2).toInt
25 |
26 | def transform(source: Bitmap): Bitmap = {
27 | val output: Bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
28 | val canvas: Canvas = new Canvas(output)
29 | val color: Int = 0xff424242
30 | val paint: Paint = new Paint
31 | val rect: Rect = new Rect(0, 0, source.getWidth, source.getHeight)
32 | val target: Rect = new Rect(0, 0, size, size)
33 | paint.setAntiAlias(true)
34 | canvas.drawARGB(0, 0, 0, 0)
35 | paint.setColor(color)
36 | canvas.drawCircle(radius, radius, radius, paint)
37 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
38 | canvas.drawBitmap(source, rect, target, paint)
39 | source.recycle()
40 | output
41 | }
42 |
43 | def key: String = {
44 | s"radius-$size"
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/components/PathMorphDrawable.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.components
2 |
3 | import android.animation.ValueAnimator.AnimatorUpdateListener
4 | import android.animation.{Animator, AnimatorListenerAdapter, ValueAnimator}
5 | import android.graphics.Paint.Style
6 | import android.graphics._
7 | import android.graphics.drawable.{Animatable, Drawable}
8 | import android.view.animation.DecelerateInterpolator
9 | import android.widget.ImageView
10 | import macroid.{ContextWrapper, Tweak}
11 | import IconTypes._
12 |
13 | import scala.util.Try
14 |
15 | class PathMorphDrawable(val defaultIcon: Int = NOICON, val defaultStroke: Int = 3, val defaultColor: Int = Color.BLACK)(implicit context: ContextWrapper)
16 | extends Drawable
17 | with Animatable
18 | with PathMorphDrawableTypes {
19 |
20 | implicit var size: Option[Dim] = None
21 |
22 | lazy val burgerIcon = List(
23 | new Segment().fromRatios(0.2f, 0.3f, 0.8f, 0.3f),
24 | new Segment().fromRatios(0.2f, 0.5f, 0.8f, 0.5f),
25 | new Segment().fromRatios(0.2f, 0.7f, 0.8f, 0.7f)
26 | )
27 |
28 | lazy val backIcon = List(
29 | new Segment().fromRatios(0.3f, 0.51f, 0.5f, 0.3f),
30 | new Segment().fromRatios(0.33f, 0.5f, 0.7f, 0.5f),
31 | new Segment().fromRatios(0.3f, 0.49f, 0.5f, 0.7f)
32 | )
33 |
34 | lazy val upIcon = List(
35 | new Segment().fromRatios(0.49f, 0.3f, 0.7f, 0.5f),
36 | new Segment().fromRatios(0.5f, 0.33f, 0.5f, 0.7f),
37 | new Segment().fromRatios(0.51f, 0.3f, 0.3f, 0.5f)
38 | )
39 |
40 | lazy val downIcon = List(
41 | new Segment().fromRatios(0.51f, 0.7f, 0.3f, 0.5f),
42 | new Segment().fromRatios(0.5f, 0.67f, 0.5f, 0.3f),
43 | new Segment().fromRatios(0.49f, 0.7f, 0.7f, 0.5f)
44 | )
45 |
46 | lazy val nextIcon = List(
47 | new Segment().fromRatios(0.7f, 0.49f, 0.5f, 0.7f),
48 | new Segment().fromRatios(0.67f, 0.5f, 0.3f, 0.5f),
49 | new Segment().fromRatios(0.7f, 0.51f, 0.5f, 0.3f)
50 | )
51 |
52 | lazy val checkIcon = List(
53 | new Segment().fromRatios(0.2f, 0.6f, 0.4f, 0.8f),
54 | new Segment().fromRatios(0.4f, 0.8f, 0.8f, 0.2f)
55 | )
56 |
57 | lazy val addIcon = List(
58 | new Segment().fromRatios(0.5f, 0.2f, 0.5f, 0.8f),
59 | new Segment().fromRatios(0.2f, 0.5f, 0.8f, 0.5f)
60 | )
61 |
62 | lazy val closeIcon = List(
63 | new Segment().fromRatios(0.712f, 0.288f, 0.288f, 0.712f),
64 | new Segment().fromRatios(0.288f, 0.288f, 0.712f, 0.712f)
65 | )
66 |
67 | val noIcon = List.empty
68 |
69 | val iconPaint: Paint = {
70 | val paint = new Paint
71 | paint.setAntiAlias(true)
72 | paint.setStyle(Style.STROKE)
73 | paint.setStrokeWidth(defaultStroke)
74 | paint.setColor(defaultColor)
75 | paint
76 | }
77 |
78 | var running = false
79 |
80 | var currentIcon: Option[Icon] = None
81 |
82 | var toIcon: Option[Icon] = None
83 |
84 | var transformIcon: Option[Icon] = None
85 |
86 | override def onBoundsChange(bounds: Rect): Unit = {
87 | super.onBoundsChange(bounds)
88 | size = Some(new Dim(bounds.width(), bounds.height()))
89 | setTypeIcon(defaultIcon)
90 | }
91 |
92 | override def draw(canvas: Canvas): Unit = {
93 | if (running) {
94 | transformIcon.map(drawIcon(canvas, _))
95 | } else {
96 | currentIcon.map(drawIcon(canvas, _))
97 | }
98 | }
99 |
100 | override def setColorFilter(cf: ColorFilter): Unit = iconPaint.setColorFilter(cf)
101 |
102 | override def setAlpha(alpha: Int): Unit = iconPaint.setAlpha(alpha)
103 |
104 | override def getOpacity: Int = PixelFormat.TRANSPARENT
105 |
106 | override def stop(): Unit = {
107 | toIcon map setIcon
108 | toIcon = None
109 | running = false
110 | }
111 |
112 | override def isRunning: Boolean = running
113 |
114 | override def start(): Unit = {
115 | (toIcon, currentIcon) match {
116 | case (Some(to), Some(current)) => running = true; moveIcon(current, to)
117 | case (Some(to), None) => setIcon(to); toIcon = None
118 | case _ => ()
119 | }
120 | }
121 |
122 | def setColor(color: Int): Unit = {
123 | iconPaint.setColor(color)
124 | invalidateSelf()
125 | }
126 |
127 | def setColorResource(color: Int): Unit = {
128 | iconPaint.setColor(context.application.getResources.getColor(color))
129 | invalidateSelf()
130 | }
131 |
132 | def setStroke(stroke: Float) = {
133 | iconPaint.setStrokeWidth(stroke)
134 | invalidateSelf()
135 | }
136 |
137 | def setTransformIcon(icon: Icon) = {
138 | transformIcon = Some(icon)
139 | invalidateSelf()
140 | }
141 |
142 | def setIcon(icon: Icon) = {
143 | currentIcon = Some(icon)
144 | invalidateSelf()
145 | }
146 |
147 | def setToIcon(icon: Icon) = {
148 | toIcon = Some(icon)
149 | }
150 |
151 | def setTypeIcon(icon: Int) = icon match {
152 | case ADD => setIcon(addIcon)
153 | case BACK => setIcon(backIcon)
154 | case BURGER => setIcon(burgerIcon)
155 | case CHECK => setIcon(checkIcon)
156 | case CLOSE => setIcon(closeIcon)
157 | case DOWN => setIcon(downIcon)
158 | case NEXT => setIcon(nextIcon)
159 | case NOICON => setIcon(noIcon)
160 | case UP => setIcon(upIcon)
161 | }
162 |
163 | def setToTypeIcon(icon: Int) = icon match {
164 | case ADD => setToIcon(addIcon)
165 | case BACK => setToIcon(backIcon)
166 | case BURGER => setToIcon(burgerIcon)
167 | case CHECK => setToIcon(checkIcon)
168 | case CLOSE => setToIcon(closeIcon)
169 | case DOWN => setToIcon(downIcon)
170 | case NEXT => setToIcon(nextIcon)
171 | case NOICON => setToIcon(noIcon)
172 | case UP => setToIcon(upIcon)
173 | }
174 |
175 | private def drawIcon(canvas: Canvas, icon: Icon): Unit = icon foreach (drawSegment(canvas, _))
176 |
177 | private def drawSegment(canvas: Canvas, segment: Segment): Unit = {
178 | iconPaint.setAlpha((segment.alpha * 255).toInt)
179 | canvas.drawLine(segment.point1.x, segment.point1.y, segment.point2.x, segment.point2.y, iconPaint)
180 | }
181 |
182 | def moveIcon(from: Icon, to: Icon) = {
183 | val valueAnimator: ValueAnimator = ValueAnimator.ofInt(0, 100)
184 | valueAnimator.addUpdateListener(new AnimatorUpdateListener {
185 | override def onAnimationUpdate(animation: ValueAnimator): Unit = {
186 | val fraction = animation.getAnimatedFraction
187 |
188 | val fromOver = from.drop(to.length)
189 | val toOver = to.drop(from.length)
190 |
191 | val transform = from.zip(to) map (i => transformSegment(i._1, i._2, fraction))
192 |
193 | val segmentFromOver = fromOver map (_.copy(alpha = 1 - fraction))
194 |
195 | val segmentToOver = toOver map { segment =>
196 | transformSegment(new Segment(
197 | Point(segment.point1.x + 1, segment.point1.y + 1),
198 | Point(segment.point1.x, segment.point1.y)), segment, fraction)
199 | }
200 |
201 | val list = transform ++ segmentFromOver ++ segmentToOver
202 |
203 | setTransformIcon(list)
204 |
205 | }
206 | })
207 | valueAnimator.setInterpolator(new DecelerateInterpolator())
208 | valueAnimator.addListener(new AnimatorListenerAdapter {
209 | override def onAnimationEnd(animation: Animator): Unit = {
210 | super.onAnimationEnd(animation)
211 | stop()
212 | }
213 | })
214 | valueAnimator.start()
215 | }
216 |
217 | def transformSegment(from: Segment, to: Segment, fraction: Float): Segment =
218 | if (from.equals(to)) {
219 | from
220 | } else {
221 | val point1 = calculatePoint(from.point1, to.point1, fraction)
222 | val point2 = calculatePoint(from.point2, to.point2, fraction)
223 |
224 | Segment(point1, point2)
225 | }
226 |
227 | def calculatePoint(from: Point, to: Point, fraction: Float): Point = {
228 | val cathetiX = to.x - from.x
229 | val cathetiY = to.y - from.y
230 |
231 | val hypotenuse = Math.sqrt((cathetiX * cathetiX) + (cathetiY * cathetiY)).toFloat
232 | val angle = Math.atan(cathetiY / cathetiX)
233 |
234 | val rFraction = hypotenuse * fraction
235 |
236 | val coordX = rFraction * Math.cos(angle).toFloat
237 | val coordY = rFraction * Math.sin(angle).toFloat
238 |
239 | if (cathetiX >= 0)
240 | Point(from.x + coordX, from.y + coordY)
241 | else
242 | Point(from.x - coordX, from.y - coordY)
243 | }
244 | }
245 |
246 | trait PathMorphDrawableTypes {
247 | type Icon = List[Segment]
248 | }
249 |
250 | object IconTypes {
251 | val NOICON = 0
252 | val BURGER = 1
253 | val BACK = 2
254 | val CHECK = 3
255 | val ADD = 4
256 | val UP = 5
257 | val DOWN = 6
258 | val NEXT = 7
259 | val CLOSE = 8
260 | }
261 |
262 | case class Dim(wight: Int, height: Int)
263 |
264 | case class Point(x: Float, y: Float)
265 |
266 | case class Segment(
267 | point1: Point = Point(0, 0),
268 | point2: Point = Point(0, 0),
269 | alpha: Float = 1) {
270 |
271 | def fromRatios(ratioX1: Float,
272 | ratioY1: Float,
273 | ratioX2: Float,
274 | ratioY2: Float)(implicit dim: Option[Dim]): Segment = {
275 | val (x1: Float, y1: Float, x2: Float, y2: Float) = dim.map {
276 | value =>
277 | val x1 = ratioX1 * value.wight
278 | val y1 = ratioY1 * value.height
279 | val x2 = ratioX2 * value.wight
280 | val y2 = ratioY2 * value.height
281 | (x1, y1, x2, y2)
282 | }.getOrElse(0f, 0f, 0f, 0f, 0f)
283 | Segment(Point(x1, y1), Point(x2, y2))
284 | }
285 | }
286 |
287 | object PathMorphDrawableTweaks {
288 | type W = ImageView
289 |
290 | def pmdAnimIcon(icon: Int) = Tweak[W] {
291 | view =>
292 | view.getDrawable.asInstanceOf[PathMorphDrawable].setToTypeIcon(icon)
293 | view.getDrawable.asInstanceOf[PathMorphDrawable].start
294 | }
295 | def pmdChangeIcon(icon: Int) = Tweak[W](view =>
296 | Try(view.getDrawable.asInstanceOf[PathMorphDrawable].setTypeIcon(icon)))
297 |
298 | def pmdColor(color: Int) = Tweak[W](view =>
299 | Try(view.getDrawable.asInstanceOf[PathMorphDrawable].setColor(color)))
300 |
301 | def pmdColorResource(color: Int) = Tweak[W](view =>
302 | Try(view.getDrawable.asInstanceOf[PathMorphDrawable].setColorResource(color)))
303 |
304 | def pmdStroke(stroke: Float) = Tweak[W](view =>
305 | Try(view.getDrawable.asInstanceOf[PathMorphDrawable].setStroke(stroke)))
306 | }
307 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/components/RippleBackgroundView.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.components
2 |
3 | import android.animation.{Animator, AnimatorListenerAdapter}
4 | import android.annotation.TargetApi
5 | import android.content.Context
6 | import android.os.Build
7 | import android.util.AttributeSet
8 | import android.view.View._
9 | import android.view.{View, ViewAnimationUtils, ViewGroup}
10 | import android.widget.FrameLayout
11 | import com.fortysevendeg.macroid.extras.SnailsUtils
12 | import com.fortysevendeg.macroid.extras.ViewTweaks._
13 | import macroid.FullDsl._
14 | import macroid.{ActivityContextWrapper, Snail}
15 |
16 | import scala.concurrent.Promise
17 |
18 | class RippleBackgroundView(context: Context, attr: AttributeSet, defStyleAttr: Int)(implicit acontext: ActivityContextWrapper)
19 | extends FrameLayout(context, attr, defStyleAttr) {
20 |
21 | def this(context: Context)(implicit acontext: ActivityContextWrapper) = this(context, null, 0)
22 |
23 | def this(context: Context, attr: AttributeSet)(implicit acontext: ActivityContextWrapper) = this(context, attr, 0)
24 |
25 | val rippleView: View = {
26 | val rippleView = getUi(
27 | w[View] <~ vInvisible
28 | )
29 | addView(rippleView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
30 | rippleView
31 | }
32 |
33 | }
34 |
35 | object RippleBackgroundSnails {
36 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
37 | def ripple(rippleData : RippleSnailData) = Snail[RippleBackgroundView] {
38 | view ⇒
39 | val animPromise = Promise[Unit]()
40 | view.rippleView.setVisibility(View.VISIBLE)
41 | view.rippleView.setBackgroundColor(rippleData.resColor)
42 | val anim = ViewAnimationUtils.createCircularReveal(view.rippleView, rippleData.x, rippleData.y, 0, rippleData.radius)
43 | anim.addListener(new AnimatorListenerAdapter {
44 | override def onAnimationEnd(animation: Animator) {
45 | super.onAnimationEnd(animation)
46 | view.rippleView.setVisibility(INVISIBLE)
47 | view.setBackgroundColor(rippleData.resColor)
48 | rippleData.onAnimationEnd foreach (listener => listener())
49 | animPromise.success()
50 | }
51 | })
52 | anim.setDuration(rippleData.duration)
53 | anim.start()
54 | animPromise.future
55 | }
56 | }
57 |
58 |
59 | case class RippleSnailData(
60 | width: Int = 0,
61 | height: Int = 0,
62 | x: Int = 0,
63 | y: Int = 0,
64 | radius: Int = 0,
65 | resColor: Int = 0,
66 | duration: Int = 300,
67 | onAnimationEnd: Option[() ⇒ Unit] = None)
68 |
69 | object RippleSnailData {
70 |
71 | def toCenterView(view : View): RippleSnailData = {
72 |
73 | val width = view.getWidth
74 |
75 | val height = view.getHeight
76 |
77 | val x = width / 2
78 |
79 | val y = height / 2
80 |
81 | val radius = SnailsUtils.calculateRadius(x, y, width, height)
82 |
83 | RippleSnailData(
84 | height = height,
85 | y = y,
86 | x = x,
87 | radius = radius)
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/googlemaps/CustomMapFragment.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.googlemaps
2 |
3 | import android.os.Bundle
4 | import com.fortysevendeg.scala.android.R
5 | import com.google.android.gms.maps.model.{MarkerOptions, LatLng}
6 | import com.google.android.gms.maps.{CameraUpdateFactory, GoogleMap, OnMapReadyCallback, SupportMapFragment}
7 | import googlemaps._
8 |
9 | trait CustomMapFragment
10 | extends SupportMapFragment {
11 |
12 | def changeMapType(mapType: Int): Unit
13 | def moveCamera(latitude: Double, longitude: Double, zoom: Int): Unit
14 | def addMarker(title: String, snippet: String, latitude: Double, longitude: Double): Unit
15 | def clearMap(): Unit
16 | }
17 |
18 | class CustomMapFragmentImpl extends CustomMapFragment with OnMapReadyCallback {
19 |
20 | var googleMap: Option[GoogleMap] = None
21 |
22 | override def onCreate(savedInstanceState: Bundle) = {
23 | super.onCreate(savedInstanceState)
24 | getMapAsync(this)
25 | }
26 |
27 | override def changeMapType(mapType: Int) = {
28 | googleMap map (_.setMapType(mapType))
29 | }
30 |
31 | override def moveCamera(latitude: Double, longitude: Double, zoom: Int = defaultZoom) = {
32 | googleMap map (_.moveCamera(
33 | CameraUpdateFactory.newLatLngZoom(new LatLng(latitude, longitude), zoom)))
34 | }
35 |
36 | override def addMarker(title: String, snippet: String, latitude: Double, longitude: Double) = {
37 | googleMap map {
38 | val marker = new MarkerOptions()
39 | .title(title)
40 | .snippet(snippet)
41 | .position(new LatLng(latitude, longitude))
42 | _.addMarker(marker)
43 | }
44 | }
45 |
46 | override def clearMap() = {
47 | googleMap map (_.clear())
48 | }
49 |
50 | override def onMapReady(googleMap: GoogleMap) = {
51 | this.googleMap = Some(googleMap)
52 | googleMap.setMyLocationEnabled(true)
53 | moveCamera(initLatitude, initLongitude)
54 | addMarker(
55 | getString(R.string.marker_map_title),
56 | getString(R.string.marker_map_message),
57 | initLatitude,
58 | initLongitude
59 | )
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/googlemaps/GoogleMapsActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.googlemaps
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.FragmentActivity
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.{Menu, MenuItem}
7 | import com.fortysevendeg.macroid.extras.FragmentExtras._
8 | import com.fortysevendeg.scala.android.R
9 | import macroid.Contexts
10 | import googlemaps._
11 |
12 | import scala.util.Random
13 |
14 | class GoogleMapsActivity
15 | extends AppCompatActivity
16 | with Contexts[FragmentActivity]
17 | with Layout {
18 |
19 | val random = new Random()
20 |
21 | override def onCreate(savedInstanceState: Bundle) = {
22 | super.onCreate(savedInstanceState)
23 |
24 | setContentView(layout)
25 |
26 | toolBar map setSupportActionBar
27 |
28 | getSupportActionBar().setDisplayHomeAsUpEnabled(true)
29 |
30 | }
31 |
32 | override def onCreateOptionsMenu(menu: Menu): Boolean = {
33 | getMenuInflater.inflate(R.menu.activity_google_maps, menu)
34 | true
35 | }
36 |
37 | override def onOptionsItemSelected(item: MenuItem): Boolean = {
38 | item.getItemId match {
39 | case android.R.id.home => {
40 | finish()
41 | false
42 | }
43 | case R.id.add_marker => {
44 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.addMarker(
45 | getString(R.string.marker_map_sample_title),
46 | getString(R.string.marker_map_sample_message),
47 | initLatitude + random.nextDouble() / 100,
48 | initLongitude + random.nextDouble() / 100))
49 | true
50 | }
51 | case R.id.clear_map => {
52 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.clearMap())
53 | true
54 | }
55 | }
56 | super.onOptionsItemSelected(item)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/googlemaps/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.googlemaps
2 |
3 | import android.support.v4.app.{FragmentManager, Fragment}
4 | import android.widget.{Button, LinearLayout}
5 | import com.fortysevendeg.scala.android.R
6 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
7 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
8 | import com.fortysevendeg.macroid.extras.TextTweaks._
9 | import com.fortysevendeg.macroid.extras.FragmentExtras._
10 | import com.google.android.gms.maps.GoogleMap
11 | import macroid.FullDsl._
12 | import macroid._
13 |
14 | trait Layout
15 | extends ToolbarLayout
16 | with IdGeneration
17 | with Styles {
18 |
19 | val fragmentTag = "fragment-map"
20 |
21 | def layout(implicit context: ActivityContextWrapper, fragmentManager: FragmentManagerContext[Fragment, FragmentManager]) = {
22 | getUi(
23 | l[LinearLayout](
24 | toolBarLayout <~ tbTitle(R.string.title_map),
25 | l[LinearLayout](
26 | w[Button] <~ tvText(R.string.map_satellite) <~ buttonsStyle <~ On.click {
27 | Ui {
28 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.changeMapType(GoogleMap.MAP_TYPE_SATELLITE))
29 | }
30 | },
31 | w[Button] <~ tvText(R.string.map_normal) <~ buttonsStyle <~ On.click {
32 | Ui {
33 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.changeMapType(GoogleMap.MAP_TYPE_NORMAL))
34 | }
35 | },
36 | w[Button] <~ tvText(R.string.map_hybrid) <~ buttonsStyle <~ On.click {
37 | Ui {
38 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.changeMapType(GoogleMap.MAP_TYPE_HYBRID))
39 | }
40 | },
41 | w[Button] <~ tvText(R.string.map_terrain) <~ buttonsStyle <~ On.click {
42 | Ui {
43 | findFragmentByTag[CustomMapFragment](fragmentTag) map (_.changeMapType(GoogleMap.MAP_TYPE_TERRAIN))
44 | }
45 | }
46 | ) <~ horizontalLinearLayoutStyle,
47 | f[CustomMapFragmentImpl].framed(Id.map, fragmentTag)
48 | ) <~ contentStyle
49 | )
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/googlemaps/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.googlemaps
2 |
3 | import android.widget.{Button, LinearLayout}
4 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
5 | import com.fortysevendeg.macroid.extras.TextTweaks._
6 | import com.fortysevendeg.macroid.extras.ViewTweaks._
7 | import macroid.Tweak
8 |
9 | import scala.language.postfixOps
10 |
11 | trait Styles {
12 |
13 | val contentStyle: Tweak[LinearLayout] = llVertical
14 |
15 | val horizontalLinearLayoutStyle: Tweak[LinearLayout] =
16 | vMatchWidth +
17 | llHorizontal
18 |
19 | val buttonsStyle: Tweak[Button] = llWrapWeightHorizontal + tvMaxLines(1)
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/googlemaps/googlemaps.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.googlemaps
2 |
3 | object googlemaps {
4 |
5 | val defaultZoom = 13
6 | val initLatitude = 36.462912
7 | val initLongitude = -6.199047
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/main/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.main
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import android.support.v7.widget.{CardView, RecyclerView}
6 | import android.widget.{ImageView, LinearLayout, TextView}
7 | import com.fortysevendeg.scala.android.R
8 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
9 | import macroid.FullDsl._
10 | import macroid.{Ui, ActivityContextWrapper}
11 | import com.fortysevendeg.macroid.extras.TextTweaks._
12 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
13 |
14 | trait Layout
15 | extends ToolbarLayout
16 | with Styles {
17 |
18 | var content = slot[LinearLayout]
19 |
20 | var recyclerView = slot[RecyclerView]
21 |
22 | def layout(implicit context: ActivityContextWrapper) = {
23 | getUi(
24 | l[LinearLayout](
25 | toolBarLayout,
26 | w[RecyclerView] <~ wire(recyclerView) <~ listStyle
27 | ) <~ wire(content) <~ contentStyle
28 | )
29 | }
30 |
31 | }
32 |
33 | class Adapter(implicit context: ActivityContextWrapper)
34 | extends AdapterStyles {
35 |
36 | var title = slot[TextView]
37 |
38 | var description = slot[TextView]
39 |
40 | var api = slot[TextView]
41 |
42 | var username = slot[TextView]
43 |
44 | var twitter = slot[TextView]
45 |
46 | var avatar = slot[ImageView]
47 |
48 | var androidLevel = slot[TextView]
49 |
50 | var scalaLevel = slot[TextView]
51 |
52 | var userContent = slot[LinearLayout]
53 |
54 | val content = {
55 | getUi(
56 | l[CardView](
57 | l[LinearLayout](
58 | l[LinearLayout](
59 | w[TextView] <~ wire(title) <~ titleStyle,
60 | w[TextView] <~ wire(api) <~ apiStyle
61 | ) <~ itemTopStyle,
62 | w[TextView] <~ wire(description) <~ descriptionStyle,
63 | w[ImageView] <~ lineHorizontalStyle,
64 | l[LinearLayout](
65 | l[LinearLayout](
66 | w[ImageView] <~ wire(avatar) <~ avatarStyle,
67 | l[LinearLayout](
68 | w[TextView] <~ wire(username) <~ userNameStyle,
69 | w[TextView] <~ wire(twitter) <~ twitterStyle
70 | ) <~ userNameContentStyle
71 | ) <~ bottomUserContentStyle <~ wire(userContent) <~ On.click {
72 | Ui {
73 | for {
74 | content <- userContent
75 | tag <- Option(content.getTag)
76 | } yield {
77 | val twitterName = if (tag.toString.startsWith("@")) tag.toString.substring(1) else tag.toString
78 | context.getOriginal.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(resGetString(R.string.url_twitter_user, twitterName))))
79 | }
80 | }
81 | },
82 | w[ImageView] <~ lineVerticalStyle,
83 | l[LinearLayout](
84 | l[LinearLayout](
85 | w[TextView] <~ levelStyle <~ tvText(R.string.scala_level),
86 | w[TextView] <~ levelTypeStyle <~ wire(scalaLevel)
87 | ) <~ levelItemContentStyle,
88 | l[LinearLayout](
89 | w[TextView] <~ levelStyle <~ tvText(R.string.android_level),
90 | w[TextView] <~ levelTypeStyle <~ wire(androidLevel)
91 | )
92 | ) <~ bottomLevelsContentStyle
93 | ) <~ bottomContentStyle
94 | ) <~ itemStyle
95 | ) <~ cardStyle
96 | )
97 | }
98 |
99 | def layout = content
100 |
101 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/main/MainActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.main
2 |
3 | import android.content.ComponentName
4 | import android.os.Bundle
5 | import android.support.v7.app.AppCompatActivity
6 | import android.support.v7.widget.{GridLayoutManager, LinearLayoutManager}
7 | import com.fortysevendeg.macroid.extras.DeviceMediaQueries._
8 | import com.fortysevendeg.macroid.extras.RecyclerViewTweaks._
9 | import com.fortysevendeg.macroid.extras.ActionsExtras._
10 | import com.fortysevendeg.scala.android.R
11 | import macroid.Contexts
12 | import macroid.FullDsl._
13 |
14 | class MainActivity
15 | extends AppCompatActivity
16 | with Contexts[AppCompatActivity]
17 | with Layout {
18 |
19 | override def onCreate(savedInstanceState: Bundle) = {
20 | super.onCreate(savedInstanceState)
21 | setContentView(layout)
22 |
23 | val adapter = new ProjectActivityInfoListAdapter(new RecyclerClickListener {
24 | override def onClick(info: ProjectActivityInfo): Unit = {
25 | if (info.apiType == APIType.REQUIRED) {
26 | aShortToast(getString(R.string.min_api_not_available))
27 | } else {
28 | aStartActivityFromComponentName(new ComponentName(getPackageName, info.className))
29 | }
30 | }
31 | })
32 |
33 | val layoutManager =
34 | landscapeTablet ?
35 | new GridLayoutManager(this, 3) |
36 | tablet ?
37 | new GridLayoutManager(this, 2) | new LinearLayoutManager(this)
38 |
39 | runUi(recyclerView <~ rvLayoutManager(layoutManager) <~
40 | rvAddItemDecoration(new MainItemDecorator) <~
41 | rvAdapter(adapter))
42 |
43 | toolBar map setSupportActionBar
44 |
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/main/MainItemDecorator.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.main
2 |
3 | import android.graphics.Rect
4 | import android.support.v7.widget.RecyclerView
5 | import android.support.v7.widget.RecyclerView.State
6 | import android.view.View
7 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
8 | import com.fortysevendeg.scala.android.R
9 | import macroid.ContextWrapper
10 |
11 | import scala.language.postfixOps
12 |
13 | class MainItemDecorator(implicit context: ContextWrapper)
14 | extends RecyclerView.ItemDecoration {
15 |
16 | override def getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: State): Unit = {
17 | outRect.top = resGetDimensionPixelSize(R.dimen.padding_default)
18 | outRect.bottom = resGetDimensionPixelSize(R.dimen.padding_default)
19 | outRect.left = resGetDimensionPixelSize(R.dimen.padding_default)
20 | outRect.right = resGetDimensionPixelSize(R.dimen.padding_default)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/main/ProjectActivityInfoListAdapter.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.main
2 |
3 | import java.io.FileNotFoundException
4 |
5 | import android.support.v7.widget.RecyclerView
6 | import android.view.View.OnClickListener
7 | import android.view.{View, ViewGroup}
8 | import com.fortysevendeg.macroid.extras.ActionsExtras._
9 | import com.fortysevendeg.macroid.extras.DeviceVersion._
10 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
11 | import com.fortysevendeg.macroid.extras.TextTweaks._
12 | import com.fortysevendeg.macroid.extras.ViewTweaks._
13 | import com.fortysevendeg.scala.android.R
14 | import com.fortysevendeg.scala.android.ui.commons.JsonReads
15 | import macroid.FullDsl._
16 | import macroid.ActivityContextWrapper
17 | import com.fortysevendeg.scala.android.ui.commons.AsyncImageTweaks._
18 | import play.api.libs.json.{JsResultException, Json}
19 |
20 | import scala.io.Source
21 | import scala.util.{Failure, Success, Try}
22 |
23 | class ProjectActivityInfoListAdapter(listener: RecyclerClickListener)
24 | (implicit context: ActivityContextWrapper)
25 | extends RecyclerView.Adapter[ViewHolder] with JsonReads {
26 |
27 | import com.fortysevendeg.scala.android.ui.main.APIType._
28 |
29 | val recyclerClickListener = listener
30 |
31 | val activityInfoList: Seq[ProjectActivityInfo] = getActivitiesFromJson("activities.json") match {
32 | case Success(activitiesList) => activitiesList
33 | case Failure(exception) => {
34 | exception match {
35 | case e: FileNotFoundException => aLongToast(context.application.getString(R.string.json_file_not_found))
36 | case e: JsResultException => aLongToast(context.application.getString(R.string.malformed_json_file))
37 | case e => aLongToast(context.application.getString(R.string.unexpected_error) + e.toString)
38 | }
39 | Seq.empty
40 | }
41 | }
42 |
43 | override def onCreateViewHolder(parentViewGroup: ViewGroup, i: Int): ViewHolder = {
44 | val adapter = new Adapter
45 | adapter.layout.setOnClickListener(new OnClickListener {
46 | override def onClick(v: View): Unit = recyclerClickListener.onClick(activityInfoList(v.getTag.asInstanceOf[Int]))
47 | })
48 |
49 | ViewHolder(adapter)
50 | }
51 |
52 | override def getItemCount: Int = activityInfoList.size
53 |
54 | override def onBindViewHolder(viewHolder: ViewHolder, position: Int): Unit = {
55 | val projectActivityInfo = activityInfoList(position)
56 | val resourceStringFormatArgs = Seq(projectActivityInfo.minApi) map (_.asInstanceOf[Object])
57 | viewHolder.content.setTag(position)
58 |
59 | val avatarSize = resGetDimensionPixelSize(R.dimen.main_list_avatar_size)
60 |
61 | runUi(
62 | (viewHolder.title <~ tvText(projectActivityInfo.name)) ~
63 | (viewHolder.userContent <~ vTag(projectActivityInfo.user.twitter)) ~
64 | (viewHolder.description <~ tvText(projectActivityInfo.description)) ~
65 | (viewHolder.avatar <~ roundedImage(projectActivityInfo.user.avatar, R.drawable.placeholder_circle, avatarSize)) ~
66 | (viewHolder.username <~ tvText(projectActivityInfo.user.name)) ~
67 | (viewHolder.twitter <~ tvText(projectActivityInfo.user.twitter)) ~
68 | (viewHolder.api <~ tvText(resGetString(R.string.min_api_required, projectActivityInfo.minApi.toString)) <~
69 | (projectActivityInfo.apiType match {
70 | case SUCCESS => vBackground(R.drawable.background_item_api_success)
71 | case ADVISED => vBackground(R.drawable.background_item_api_advised)
72 | case _ => vBackground(R.drawable.background_item_api_required)
73 | })) ~
74 | (viewHolder.androidLevel <~ (projectActivityInfo.androidLevel match {
75 | case 1 => tvText(R.string.beginning_level)
76 | case 2 => tvText(R.string.intermediate_level)
77 | case 3 => tvText(R.string.advanced_level)
78 | })) ~
79 | (viewHolder.scalaLevel <~ (projectActivityInfo.scalaLevel match {
80 | case 1 => tvText(R.string.beginning_level)
81 | case 2 => tvText(R.string.intermediate_level)
82 | case 3 => tvText(R.string.advanced_level)
83 | }))
84 | )
85 | }
86 |
87 | private def getActivitiesFromJson(url: String): Try[Seq[ProjectActivityInfo]] = {
88 | for {
89 | stream <- Try(context.application.getAssets.open(url))
90 | json <- Try(Source.fromInputStream(stream, "UTF-8").mkString)
91 | activityInfoList <- Try(Json.parse(json).as[Seq[ProjectActivityInfo]])
92 | } yield activityInfoList
93 | }
94 |
95 | }
96 |
97 | trait RecyclerClickListener {
98 | def onClick(info: ProjectActivityInfo)
99 | }
100 |
101 | case class ViewHolder(adapter: Adapter)(implicit context: ActivityContextWrapper)
102 | extends RecyclerView.ViewHolder(adapter.layout) {
103 |
104 | val content = adapter.layout
105 |
106 | val title = adapter.title
107 |
108 | val description = adapter.description
109 |
110 | val api = adapter.api
111 |
112 | val username = adapter.username
113 |
114 | val twitter = adapter.twitter
115 |
116 | val avatar = adapter.avatar
117 |
118 | val scalaLevel = adapter.scalaLevel
119 |
120 | val androidLevel = adapter.androidLevel
121 |
122 | val userContent = adapter.userContent
123 |
124 | }
125 |
126 | case class ProjectActivityInfo(
127 | name: String,
128 | description: String,
129 | className: String,
130 | minApi: Int,
131 | targetApi: Int,
132 | scalaLevel: Int,
133 | androidLevel: Int,
134 | user: UserInfo) {
135 |
136 | import com.fortysevendeg.scala.android.ui.main.APIType._
137 |
138 | val apiType: APIType.Value = CurrentVersion match {
139 | case current if current.version >= targetApi => SUCCESS
140 | case current if current.version >= minApi => ADVISED
141 | case _ => REQUIRED
142 | }
143 | }
144 |
145 | case class UserInfo(
146 | avatar: String,
147 | name: String,
148 | twitter: String)
149 |
150 | object APIType extends Enumeration {
151 |
152 | type APIType = Value
153 |
154 | val SUCCESS, ADVISED, REQUIRED = Value
155 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/main/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.main
2 |
3 | import android.support.v7.widget.{CardView, RecyclerView}
4 | import android.text.TextUtils.TruncateAt
5 | import android.view.Gravity
6 | import android.view.ViewGroup.LayoutParams._
7 | import android.widget.ImageView.ScaleType
8 | import android.widget.{ImageView, LinearLayout, TextView}
9 | import com.fortysevendeg.macroid.extras.FrameLayoutTweaks._
10 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
11 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
12 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
13 | import com.fortysevendeg.macroid.extras.TextTweaks._
14 | import com.fortysevendeg.macroid.extras.ThemeExtras._
15 | import com.fortysevendeg.macroid.extras.ViewGroupTweaks._
16 | import com.fortysevendeg.macroid.extras.ViewTweaks._
17 | import com.fortysevendeg.scala.android.R
18 | import macroid.FullDsl._
19 | import macroid.{ActivityContextWrapper, ContextWrapper, Tweak}
20 |
21 | import scala.language.postfixOps
22 |
23 | trait Styles {
24 |
25 | def listStyle(implicit context: ContextWrapper): Tweak[RecyclerView] =
26 | llMatchWeightVertical +
27 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
28 | vgClipToPadding(false)
29 |
30 | def contentStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
31 | llVertical +
32 | vBackgroundColorResource(R.color.main_list_background)
33 |
34 | }
35 |
36 | trait AdapterStyles {
37 |
38 | def cardStyle(implicit activityContext: ActivityContextWrapper): Tweak[CardView] =
39 | vMatchWidth +
40 | (themeGetDrawable(android.R.attr.selectableItemBackground) map flForeground getOrElse Tweak.blank)
41 |
42 | def itemStyle: Tweak[LinearLayout] =
43 | llVertical +
44 | vMatchWidth
45 |
46 | def itemTopStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
47 | llHorizontal +
48 | vMatchWidth +
49 | llGravity(Gravity.CENTER_VERTICAL) +
50 | vPadding(
51 | paddingTop = resGetDimensionPixelSize(R.dimen.padding_default_xlarge),
52 | paddingBottom = resGetDimensionPixelSize(R.dimen.padding_default),
53 | paddingLeft = resGetDimensionPixelSize(R.dimen.padding_default_xlarge),
54 | paddingRight = resGetDimensionPixelSize(R.dimen.padding_default_xlarge))
55 |
56 | def titleStyle(implicit context: ContextWrapper): Tweak[TextView] =
57 | llWrapWeightHorizontal +
58 | tvSizeResource(R.dimen.font_size_medium) +
59 | tvColorResource(R.color.primary)
60 |
61 | def descriptionStyle(implicit context: ContextWrapper): Tweak[TextView] =
62 | tvSizeResource(R.dimen.font_size_normal) +
63 | tvNormalLight +
64 | tvColorResource(R.color.main_list_description) +
65 | tvMaxLines(3) +
66 | vPadding(
67 | paddingBottom = resGetDimensionPixelSize(R.dimen.padding_default_xlarge),
68 | paddingLeft = resGetDimensionPixelSize(R.dimen.padding_default_xlarge),
69 | paddingRight = resGetDimensionPixelSize(R.dimen.padding_default_xlarge))
70 |
71 | def apiStyle(implicit context: ContextWrapper): Tweak[TextView] =
72 | tvSizeResource(R.dimen.font_size_micro) +
73 | tvColorResource(R.color.main_list_api) +
74 | vPaddings(
75 | paddingTopBottom = resGetDimensionPixelSize(R.dimen.padding_default_micro),
76 | paddingLeftRight = resGetDimensionPixelSize(R.dimen.padding_default_small))
77 |
78 | def lineHorizontalStyle(implicit context: ContextWrapper): Tweak[ImageView] =
79 | lp[LinearLayout](MATCH_PARENT, resGetDimensionPixelSize(R.dimen.line)) +
80 | vBackgroundColorResource(R.color.main_list_line)
81 |
82 | val bottomContentStyle: Tweak[LinearLayout] =
83 | vMatchWidth +
84 | llHorizontal +
85 | llGravity(Gravity.CENTER_VERTICAL)
86 |
87 | def bottomUserContentStyle(implicit activityContext: ActivityContextWrapper): Tweak[LinearLayout] =
88 | llMatchWeightHorizontal +
89 | llHorizontal +
90 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
91 | llGravity(Gravity.CENTER_VERTICAL) +
92 | (themeGetDrawable(android.R.attr.selectableItemBackground) map vBackground getOrElse Tweak.blank)
93 |
94 | def avatarStyle(implicit context: ContextWrapper): Tweak[ImageView] = {
95 | val size = resGetDimensionPixelSize(R.dimen.main_list_avatar_size)
96 | lp[LinearLayout](size, size) +
97 | ivScaleType(ScaleType.CENTER_CROP) +
98 | vMargins(resGetDimensionPixelSize(R.dimen.padding_default_small))
99 | }
100 |
101 | def userNameStyle(implicit context: ContextWrapper): Tweak[TextView] =
102 | tvSizeResource(R.dimen.font_size_normal) +
103 | tvNormalLight +
104 | tvColorResource(R.color.primary) +
105 | tvMaxLines(1) +
106 | tvEllipsize(TruncateAt.END)
107 |
108 | def twitterStyle(implicit context: ContextWrapper): Tweak[TextView] =
109 | tvSizeResource(R.dimen.font_size_small) +
110 | tvNormalLight +
111 | tvColorResource(R.color.main_list_secondary) +
112 | tvMaxLines(1) +
113 | tvEllipsize(TruncateAt.END)
114 |
115 | def userNameContentStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
116 | llMatchWeightHorizontal +
117 | llVertical +
118 | vPadding(paddingLeft = resGetDimensionPixelSize(R.dimen.padding_default_small)) +
119 | llGravity(Gravity.CENTER_VERTICAL)
120 |
121 | def lineVerticalStyle(implicit context: ContextWrapper): Tweak[ImageView] =
122 | lp[LinearLayout](resGetDimensionPixelSize(R.dimen.line), MATCH_PARENT) +
123 | vBackgroundColorResource(R.color.main_list_line)
124 |
125 | def bottomLevelsContentStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
126 | llMatchWeightHorizontal +
127 | llVertical +
128 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
129 | llGravity(Gravity.CENTER_VERTICAL)
130 |
131 | def levelItemContentStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
132 | vWrapContent +
133 | vPadding(paddingBottom = resGetDimensionPixelSize(R.dimen.padding_default_micro))
134 |
135 | def levelStyle(implicit context: ContextWrapper): Tweak[TextView] =
136 | tvSizeResource(R.dimen.font_size_small) +
137 | tvNormalLight +
138 | tvColorResource(R.color.main_list_secondary) +
139 | vMinWidth(resGetDimensionPixelSize(R.dimen.main_list_min_width_levels_tag))
140 |
141 | def levelTypeStyle(implicit context: ContextWrapper): Tweak[TextView] =
142 | tvSizeResource(R.dimen.font_size_small) +
143 | tvColorResource(R.color.main_list_tag) +
144 | tvNormalLight +
145 | vPaddings(
146 | paddingTopBottom = 0,
147 | paddingLeftRight = resGetDimensionPixelSize(R.dimen.padding_default_small)) +
148 | tvMaxLines(1) +
149 | tvEllipsize(TruncateAt.END)
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/materiallist/Composers.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.materiallist
2 |
3 | import android.graphics.Color
4 | import android.support.design.widget.Snackbar
5 | import android.support.v7.widget.{GridLayoutManager, RecyclerView}
6 | import android.view.View
7 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
8 | import com.fortysevendeg.macroid.extras.RecyclerViewTweaks._
9 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
10 | import com.fortysevendeg.macroid.extras.TextTweaks._
11 | import com.fortysevendeg.macroid.extras.ViewTweaks._
12 | import com.fortysevendeg.scala.android.ui.commons.AsyncImageTweaks._
13 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
14 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawable
15 | import com.fortysevendeg.scala.android.{R, TR, TypedFindView}
16 | import macroid.FullDsl._
17 | import macroid.{ActivityContextWrapper, ContextWrapper, Ui}
18 |
19 | import scala.language.postfixOps
20 |
21 | trait MainComposer {
22 |
23 | self: TypedFindView =>
24 |
25 | lazy val content = Option(findView(TR.content))
26 |
27 | lazy val toolBar = Option(findView(TR.toolbar))
28 |
29 | lazy val appBarLayout = Option(findView(TR.app_bar_layout))
30 |
31 | lazy val recycler = Option(findView(TR.recycler))
32 |
33 | lazy val fabActionButton = Option(findView(TR.fab_action_button))
34 |
35 | def composition(implicit contextWrapper: ActivityContextWrapper): Ui[_] = initRecycler ~ initFabButton
36 |
37 | private[this] def initRecycler(implicit contextWrapper: ActivityContextWrapper): Ui[_] =
38 | (recycler
39 | <~ rvAdapter(new ImageListAdapter())
40 | <~ rvFixedSize
41 | <~ rvLayoutManager(new GridLayoutManager(contextWrapper.bestAvailable, 2)))
42 |
43 | private[this] def initFabButton(implicit contextWrapper: ContextWrapper): Ui[_] =
44 | (fabActionButton
45 | <~ ivSrc(fabDrawable)
46 | <~ On.click {
47 | Ui {
48 | content foreach (Snackbar.make(_, resGetString(R.string.material_list_add_item), Snackbar.LENGTH_LONG).show())
49 | }
50 | })
51 |
52 | private[this] def fabDrawable(implicit contextWrapper: ContextWrapper) = new PathMorphDrawable(
53 | defaultIcon = ADD,
54 | defaultStroke = resGetDimensionPixelSize(R.dimen.circular_reveal_fab_stroke),
55 | defaultColor = Color.WHITE
56 | )
57 |
58 | }
59 |
60 | case class ImageViewHolder(parent: View)
61 | extends RecyclerView.ViewHolder(parent)
62 | with TypedFindView {
63 |
64 | lazy val image = Option(findView(TR.image))
65 | lazy val text = Option(findView(TR.text))
66 |
67 | override protected def findViewById(id: Int): View = parent.findViewById(id)
68 |
69 | def bind(imageData: ImageData, position: Int)(implicit contextWrapper: ActivityContextWrapper): Ui[_] =
70 | (parent <~ vTag(position.toString)) ~
71 | (image <~ srcImage(imageData.url)) ~
72 | (text <~ tvText(imageData.name))
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/materiallist/FABAnimationBehavior.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.materiallist
2 |
3 | import android.animation.{Animator, AnimatorListenerAdapter}
4 | import android.content.Context
5 | import android.support.design.widget.{CoordinatorLayout, FloatingActionButton}
6 | import android.support.v4.view.ViewCompat
7 | import android.support.v4.view.animation.FastOutSlowInInterpolator
8 | import android.util.AttributeSet
9 | import android.view.View
10 | import macroid.Snail
11 | import macroid.FullDsl._
12 | import scala.concurrent.ExecutionContext.Implicits.global
13 |
14 | import scala.concurrent.Promise
15 |
16 | class FABAnimationBehavior
17 | extends FloatingActionButton.Behavior {
18 |
19 | def this(context: Context, attrs: AttributeSet) = this()
20 |
21 | var isAnimatingOut = false
22 |
23 | val interpolator = new FastOutSlowInInterpolator()
24 |
25 | val duration = 200L
26 |
27 | override def onStartNestedScroll(
28 | coordinatorLayout: CoordinatorLayout,
29 | child: FloatingActionButton,
30 | directTargetChild: View,
31 | target: View,
32 | nestedScrollAxes: Int): Boolean =
33 | nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
34 | super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes)
35 |
36 | override def onNestedScroll(
37 | coordinatorLayout: CoordinatorLayout,
38 | child: FloatingActionButton,
39 | target: View,
40 | dxConsumed: Int,
41 | dyConsumed: Int,
42 | dxUnconsumed: Int,
43 | dyUnconsumed: Int): Unit = {
44 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
45 |
46 | (dyConsumed, child) match {
47 | case (d, c) if d > 0 && !isAnimatingOut && c.getVisibility == View.VISIBLE =>
48 | runUi(Option(child) <~~ animateOut)
49 | case (d, c) if d < 0 && c.getVisibility != View.VISIBLE =>
50 | runUi(Option(child) <~~ animateIn)
51 | case _ =>
52 | }
53 | }
54 |
55 | val animateIn = Snail[FloatingActionButton] {
56 | view ⇒
57 | view.setVisibility(View.VISIBLE)
58 | val animPromise = Promise[Unit]()
59 | view.animate
60 | .translationY(0)
61 | .setInterpolator(interpolator)
62 | .setDuration(duration)
63 | .setListener(new AnimatorListenerAdapter {
64 | override def onAnimationEnd(animation: Animator) {
65 | super.onAnimationEnd(animation)
66 | animPromise.success()
67 | }
68 | }).start()
69 | animPromise.future
70 | }
71 |
72 | val animateOut = Snail[FloatingActionButton] {
73 | view ⇒
74 | val animPromise = Promise[Unit]()
75 | val y = view.getHeight + (view.getPaddingBottom * 2)
76 | view.animate
77 | .translationY(y)
78 | .setInterpolator(interpolator)
79 | .setDuration(duration)
80 | .setListener(new AnimatorListenerAdapter {
81 | override def onAnimationStart(animation: Animator): Unit = {
82 | super.onAnimationStart(animation)
83 | isAnimatingOut = true
84 | }
85 | override def onAnimationCancel(animation: Animator): Unit = {
86 | super.onAnimationCancel(animation)
87 | isAnimatingOut = false
88 | }
89 | override def onAnimationEnd(animation: Animator) {
90 | super.onAnimationEnd(animation)
91 | isAnimatingOut = false
92 | view.setVisibility(View.GONE)
93 | animPromise.success()
94 | }
95 | }).start()
96 | animPromise.future
97 | }
98 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/materiallist/ImageListAdapter.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.materiallist
2 |
3 | import android.support.design.widget.Snackbar
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.{LayoutInflater, View, ViewGroup}
6 | import com.fortysevendeg.scala.android.R
7 | import macroid.ActivityContextWrapper
8 | import macroid.FullDsl._
9 |
10 | class ImageListAdapter(implicit context: ActivityContextWrapper)
11 | extends RecyclerView.Adapter[ImageViewHolder]
12 | with View.OnClickListener {
13 |
14 | val images: Seq[ImageData] = 1 to 10 map {
15 | i =>
16 | ImageData(s"Item $i", s"http://lorempixel.com/500/500/animals/$i")
17 | }
18 |
19 | override def onCreateViewHolder(parent: ViewGroup, i: Int): ImageViewHolder = {
20 | val v = LayoutInflater.from(parent.getContext).inflate(R.layout.image_item, parent, false)
21 | v.setOnClickListener(this)
22 | new ImageViewHolder(v)
23 | }
24 |
25 | override def getItemCount: Int = images.size
26 |
27 | override def onBindViewHolder(viewHolder: ImageViewHolder, position: Int): Unit =
28 | runUi(viewHolder.bind(images(position), position))
29 |
30 | override def onClick(v: View): Unit = {
31 | val image = images(v.getTag.toString.toInt)
32 | Snackbar.make(v, image.name, Snackbar.LENGTH_LONG).show()
33 | }
34 | }
35 |
36 | case class ImageData(name: String, url: String)
37 |
38 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/materiallist/MaterialListActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.materiallist
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.view.MenuItem
6 | import com.fortysevendeg.scala.android.{R, TypedFindView}
7 | import macroid.Contexts
8 | import macroid.FullDsl._
9 |
10 | class MaterialListActivity
11 | extends AppCompatActivity
12 | with TypedFindView
13 | with MainComposer
14 | with Contexts[AppCompatActivity] {
15 |
16 | override def onCreate(savedInstanceState: Bundle) = {
17 | super.onCreate(savedInstanceState)
18 |
19 | setContentView(R.layout.material_list_activity)
20 |
21 | toolBar foreach setSupportActionBar
22 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
23 |
24 | runUi(composition)
25 | }
26 |
27 | override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
28 | case android.R.id.home =>
29 | finish()
30 | false
31 | case _ => super.onOptionsItemSelected(item)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/pathmorphing/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.pathmorphing
2 |
3 | import android.graphics.Color
4 | import android.view.View
5 | import android.widget._
6 | import com.fortysevendeg.scala.android.R
7 | import com.fortysevendeg.macroid.extras.SeekBarEventsExtras.OnSeekBarChangeListenerHandler
8 | import com.fortysevendeg.macroid.extras.SeekBarTweaks._
9 | import com.fortysevendeg.macroid.extras.TextTweaks._
10 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
11 | import com.fortysevendeg.macroid.extras.ViewTweaks._
12 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
13 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawableTweaks._
14 | import com.fortysevendeg.scala.android.ui.components.Dim
15 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
16 | import macroid.FullDsl._
17 | import macroid.{ActivityContextWrapper, Transformer}
18 | import scala.language.postfixOps
19 |
20 | trait Layout
21 | extends ToolbarLayout
22 | with Styles {
23 |
24 | val sizeOptionList = List(Dim(48, 48), Dim(72, 72), Dim(96, 96), Dim(128, 128))
25 |
26 | var icon = slot[ImageView]
27 | var sampleColor1Icon = slot[ImageView]
28 | var sampleColor2Icon = slot[ImageView]
29 | var sampleColor3Icon = slot[ImageView]
30 | var sampleColor4Icon = slot[ImageView]
31 | var iconSelectorGroup = slot[LinearLayout]
32 | var colorSelectorGroup = slot[LinearLayout]
33 | var strokeTitle = slot[TextView]
34 | var sizeTitle = slot[TextView]
35 | var strokeSelector = slot[SeekBar]
36 | var sizeSelector = slot[SeekBar]
37 |
38 | val deactivateImageViewWidgets = Transformer {
39 | case i: ImageView => i <~ vActivated(false)
40 | }
41 |
42 | val setNoIconImageViewWidgets = Transformer {
43 | case i: ImageView => i <~ pmdAnimIcon(NOICON)
44 | }
45 |
46 | def layout(implicit context: ActivityContextWrapper) = {
47 | getUi(
48 | l[LinearLayout](
49 | toolBarLayout <~ tbTitle(R.string.title_path_morphing),
50 | l[LinearLayout](
51 | l[FrameLayout](
52 | w[ImageView] <~ drawableStyle(width = 48 dp, height = 48 dp, stroke = 3 dp) <~ wire(icon)
53 | ) <~ drawableContentStyle,
54 | l[ScrollView](
55 | l[LinearLayout](
56 | w[TextView] <~ tvText(R.string.title_select_icon) <~ titleStyle,
57 | l[LinearLayout](
58 | l[LinearLayout](
59 | w[ImageView] <~ iconSelectorStyle(BURGER, false) <~ FuncOn.click {
60 | (view: View) => {
61 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
62 | (icon <~ pmdAnimIcon(BURGER)) ~
63 | (view <~ vActivated(true))
64 | }
65 | },
66 | w[ImageView] <~ iconSelectorStyle(CHECK, false) <~ FuncOn.click {
67 | (view: View) => {
68 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
69 | (icon <~ pmdAnimIcon(CHECK)) ~
70 | (view <~ vActivated(true))
71 | }
72 | },
73 | w[ImageView] <~ iconSelectorStyle(ADD, false) <~ FuncOn.click {
74 | (view: View) => {
75 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
76 | (icon <~ pmdAnimIcon(ADD)) ~
77 | (view <~ vActivated(true))
78 | }
79 | },
80 | w[ImageView] <~ iconSelectorStyle(CLOSE, false) <~ FuncOn.click {
81 | (view: View) => {
82 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
83 | (icon <~ pmdAnimIcon(CLOSE)) ~
84 | (view <~ vActivated(true))
85 | }
86 | }
87 | ) <~ tableLayoutRowStyle,
88 | l[LinearLayout](
89 | w[ImageView] <~ iconSelectorStyle(UP, false) <~ FuncOn.click {
90 | (view: View) => {
91 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
92 | (icon <~ pmdAnimIcon(UP)) ~
93 | (view <~ vActivated(true))
94 | }
95 | },
96 | w[ImageView] <~ iconSelectorStyle(NEXT, false) <~ FuncOn.click {
97 | (view: View) => {
98 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
99 | (icon <~ pmdAnimIcon(NEXT)) ~
100 | (view <~ vActivated(true))
101 | }
102 | },
103 | w[ImageView] <~ iconSelectorStyle(DOWN, false) <~ FuncOn.click {
104 | (view: View) => {
105 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
106 | (icon <~ pmdAnimIcon(DOWN)) ~
107 | (view <~ vActivated(true))
108 | }
109 | },
110 | w[ImageView] <~ iconSelectorStyle(BACK, false) <~ FuncOn.click {
111 | (view: View) => {
112 | (iconSelectorGroup <~ deactivateImageViewWidgets) ~
113 | (icon <~ pmdAnimIcon(BACK)) ~
114 | (view <~ vActivated(true))
115 | }
116 | }
117 | ) <~ tableLayoutRowStyle
118 | ) <~ tableLayoutStyle <~ wire(iconSelectorGroup),
119 | w[TextView] <~ tvText(R.string.title_select_color) <~ titleStyle,
120 | l[LinearLayout](
121 | l[LinearLayout](
122 | w[ImageView] <~ colorSelectorStyle(true) <~ wire(sampleColor1Icon) <~ pmdColor(Color.WHITE) <~ vBackground(R.drawable.background_item_pathmorphsample_1) <~ On.click {
123 | (icon <~ pmdColorResource(R.color.path_morph_sample_1)) ~
124 | (colorSelectorGroup <~ setNoIconImageViewWidgets) ~
125 | (sampleColor1Icon <~ pmdAnimIcon(CHECK))
126 | },
127 | w[ImageView] <~ colorSelectorStyle(false) <~ wire(sampleColor2Icon) <~ vBackground(R.drawable.background_item_pathmorphsample_2) <~ On.click {
128 | (icon <~ pmdColorResource(R.color.path_morph_sample_2)) ~
129 | (colorSelectorGroup <~ setNoIconImageViewWidgets) ~
130 | (sampleColor2Icon <~ pmdAnimIcon(CHECK))
131 | },
132 | w[ImageView] <~ colorSelectorStyle(false) <~ wire(sampleColor3Icon) <~ vBackground(R.drawable.background_item_pathmorphsample_3) <~ On.click {
133 | (icon <~ pmdColorResource(R.color.path_morph_sample_3)) ~
134 | (colorSelectorGroup <~ setNoIconImageViewWidgets) ~
135 | (sampleColor3Icon <~ pmdAnimIcon(CHECK))
136 | },
137 | w[ImageView] <~ colorSelectorStyle(false) <~ wire(sampleColor4Icon) <~ pmdColor(Color.WHITE) <~ vBackground(R.drawable.background_item_pathmorphsample_4) <~ On.click {
138 | (icon <~ pmdColorResource(R.color.path_morph_sample_4)) ~
139 | (colorSelectorGroup <~ setNoIconImageViewWidgets) ~
140 | (sampleColor4Icon <~ pmdAnimIcon(CHECK))
141 | }
142 | ) <~ tableLayoutRowStyle
143 | ) <~ tableLayoutStyle <~ wire(colorSelectorGroup),
144 | w[TextView] <~ wire(strokeTitle) <~ tvText(R.string.title_select_stroke) <~ titleStyle,
145 | w[SeekBar] <~ wire(strokeSelector) <~ strokeStyle <~ sbOnSeekBarChangeListener(
146 | OnSeekBarChangeListenerHandler(
147 | onProgressChangedHandler = (view: SeekBar, progress: Int, fromUser: Boolean) => {
148 | val stroke = progress + 1
149 |
150 | (strokeTitle <~ tvText(context.application.getResources().getString(R.string.title_select_stroke, stroke.toString))) ~
151 | (icon <~ pmdStroke(stroke dp))
152 | }
153 | )
154 | ),
155 | w[TextView] <~ wire(sizeTitle) <~ tvText(R.string.title_select_size) <~ titleStyle,
156 | w[SeekBar] <~ wire(sizeSelector) <~ sizeStyle <~ sbOnSeekBarChangeListener(
157 | OnSeekBarChangeListenerHandler(
158 | onStopTrackingTouchHandler = (view: SeekBar) => {
159 | val Dim(width, height) = sizeOptionList(view.getProgress())
160 |
161 | (sizeTitle <~ tvText(context.application.getResources().getString(R.string.title_select_size, width.toString, height.toString))) ~
162 | (colorSelectorGroup <~ setNoIconImageViewWidgets) ~
163 | (sampleColor1Icon <~ pmdAnimIcon(CHECK)) ~
164 | (strokeSelector <~ sbProgress(2)) ~
165 | (icon <~ drawableStyle(width dp, height dp, 3 dp) <~ pmdColorResource(R.color.path_morph_sample_1)) ~
166 | (iconSelectorGroup <~ deactivateImageViewWidgets)
167 | }
168 | )
169 | )
170 | ) <~ verticalLinearLayoutStyle
171 | ) <~ scrollViewStyle
172 | ) <~ contentStyle
173 | ) <~ rootStyle
174 | )
175 | }
176 |
177 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/pathmorphing/PathMorphingActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.pathmorphing
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.view.MenuItem
6 | import com.fortysevendeg.scala.android.R
7 | import com.fortysevendeg.macroid.extras.SeekBarTweaks._
8 | import com.fortysevendeg.macroid.extras.TextTweaks._
9 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawableTweaks._
10 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
11 | import macroid.Contexts
12 | import macroid.FullDsl._
13 |
14 | class PathMorphingActivity
15 | extends AppCompatActivity
16 | with Contexts[AppCompatActivity]
17 | with Layout {
18 |
19 | override def onCreate(savedInstanceState: Bundle) = {
20 | super.onCreate(savedInstanceState)
21 |
22 | setContentView(layout)
23 |
24 | toolBar foreach setSupportActionBar
25 |
26 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
27 |
28 | runUi(
29 | (icon <~ pmdAnimIcon(BURGER)) ~
30 | (strokeSelector <~ sbProgress(2)) ~
31 | (sizeTitle <~ tvText(getString(R.string.title_select_size, 48.toString, 48.toString)))
32 | )
33 | }
34 |
35 | override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
36 | case android.R.id.home =>
37 | finish()
38 | false
39 | case _ => super.onOptionsItemSelected(item)
40 | }
41 | }
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/pathmorphing/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.pathmorphing
2 |
3 | import android.view.Gravity
4 | import android.widget._
5 | import com.fortysevendeg.scala.android.R
6 | import com.fortysevendeg.macroid.extras.ImageViewTweaks._
7 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
8 | import com.fortysevendeg.macroid.extras.FrameLayoutTweaks._
9 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
10 | import com.fortysevendeg.macroid.extras.SeekBarTweaks._
11 | import com.fortysevendeg.macroid.extras.TextTweaks._
12 | import com.fortysevendeg.macroid.extras.ViewTweaks._
13 | import com.fortysevendeg.scala.android.ui.components.PathMorphDrawable
14 | import com.fortysevendeg.scala.android.ui.components.IconTypes._
15 | import macroid.{Tweak, ContextWrapper}
16 | import macroid.FullDsl._
17 | import scala.language.postfixOps
18 |
19 | trait Styles {
20 |
21 | val rootStyle: Tweak[LinearLayout] =
22 | vMatchParent +
23 | llVertical
24 |
25 | val contentStyle: Tweak[LinearLayout] =
26 | vMatchParent +
27 | llVertical
28 |
29 | def scrollViewStyle(implicit context: ContextWrapper): Tweak[ScrollView] =
30 | vMatchParent +
31 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
32 |
33 | val horizontalLinearLayoutStyle: Tweak[LinearLayout] = llHorizontal
34 |
35 | val verticalLinearLayoutStyle: Tweak[LinearLayout] =
36 | vMatchParent +
37 | llVertical
38 |
39 | val contentItemsRadiosStyle: Tweak[LinearLayout] = llMatchWeightHorizontal
40 |
41 | val titleStyle: Tweak[TextView] = tvNormalLight
42 |
43 | val strokeStyle: Tweak[SeekBar] =
44 | sbMax(4) +
45 | sbProgress(0) +
46 | vMatchWidth
47 |
48 | val sizeStyle: Tweak[SeekBar] =
49 | sbMax(3) +
50 | sbProgress(0) +
51 | vMatchWidth
52 |
53 | def drawableContentStyle(implicit context: ContextWrapper): Tweak[FrameLayout] = {
54 | val size = resGetDimensionPixelSize(R.dimen.path_morphing_size_content_drawable)
55 | lp[LinearLayout](size, size) +
56 | llLayoutGravity(Gravity.CENTER)
57 | }
58 |
59 |
60 | def drawableStyle(width: Int, height: Int, stroke: Int)(implicit context: ContextWrapper): Tweak[ImageView] =
61 | lp[LinearLayout](width, height) +
62 | flLayoutGravity(Gravity.CENTER) +
63 | vMargins(resGetDimensionPixelSize(R.dimen.padding_default)) +
64 | ivSrc(new PathMorphDrawable(defaultStroke = stroke)) +
65 | vBackground(R.drawable.background_item_square)
66 |
67 | def colorSelectorStyle(selected: Boolean = false)(implicit context: ContextWrapper): Tweak[ImageView] = {
68 | val size = resGetDimensionPixelSize(R.dimen.path_morphing_size_circles)
69 | lp[LinearLayout](size, size) +
70 | vPaddings(8 dp) +
71 | vMargins(8 dp) +
72 | (selected match {
73 | case true => ivSrc(new PathMorphDrawable(defaultIcon = CHECK, defaultStroke = 2 dp))
74 | case _ => ivSrc(new PathMorphDrawable(defaultStroke = 2 dp))
75 | })
76 | }
77 |
78 | def iconSelectorStyle(icon: Int, selected: Boolean = false)(implicit context: ContextWrapper): Tweak[ImageView] = {
79 | val size = resGetDimensionPixelSize(R.dimen.path_morphing_size_circles)
80 | lp[LinearLayout](size, size) +
81 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default)) +
82 | vBackground(R.drawable.background_item_icon_selector) +
83 | vMargins(resGetDimensionPixelSize(R.dimen.padding_default)) +
84 | ivSrc(new PathMorphDrawable(defaultIcon = icon, defaultStroke = 2 dp)) +
85 | vActivated(selected)
86 | }
87 |
88 | val tableLayoutStyle: Tweak[LinearLayout] =
89 | vMatchWidth +
90 | llVertical
91 |
92 | def tableLayoutRowStyle(implicit context: ContextWrapper): Tweak[LinearLayout] =
93 | vMatchWidth +
94 | llHorizontal +
95 | llGravity(Gravity.CENTER)
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/ripplebackground/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.ripplebackground
2 |
3 | import android.widget.{FrameLayout, LinearLayout}
4 | import com.fortysevendeg.scala.android.R
5 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
6 | import com.fortysevendeg.scala.android.ui.components.{CircleView, RippleBackgroundView}
7 | import Styles._
8 | import macroid.ActivityContextWrapper
9 | import macroid.FullDsl._
10 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
11 |
12 | trait Layout extends ToolbarLayout {
13 |
14 | var rippleBackground = slot[RippleBackgroundView]
15 |
16 | var circle1 = slot[CircleView]
17 |
18 | var circle2 = slot[CircleView]
19 |
20 | var circle3 = slot[CircleView]
21 |
22 | def layout(implicit context: ActivityContextWrapper) = getUi(
23 | l[LinearLayout](
24 | toolBarLayout <~ tbTitle(R.string.title_ripple_background),
25 | l[FrameLayout](
26 | w[RippleBackgroundView] <~ wire(rippleBackground) <~ backgroundStyle,
27 | l[LinearLayout](
28 | w[CircleView] <~ wire(circle1) <~ circleStyle,
29 | w[CircleView] <~ wire(circle2) <~ circleStyle,
30 | w[CircleView] <~ wire(circle3) <~ circleStyle
31 | ) <~ circlesContentStyle
32 | ) <~ colorContent
33 | ) <~ rootStyle
34 | )
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/ripplebackground/RippleBackgroundActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.ripplebackground
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.view.MenuItem
6 | import com.fortysevendeg.macroid.extras.MoveSnails._
7 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
8 | import com.fortysevendeg.macroid.extras.ViewTweaks._
9 | import com.fortysevendeg.scala.android.R
10 | import com.fortysevendeg.scala.android.ui.components.CircleView._
11 | import com.fortysevendeg.scala.android.ui.components.RippleBackgroundSnails._
12 | import com.fortysevendeg.scala.android.ui.components.{CircleView, RippleSnailData}
13 | import macroid.FullDsl._
14 | import macroid.{Contexts, Ui}
15 |
16 | import scala.concurrent.ExecutionContext.Implicits.global
17 |
18 | class RippleBackgroundActivity
19 | extends AppCompatActivity
20 | with Contexts[AppCompatActivity]
21 | with Layout {
22 |
23 | override def onCreate(savedInstanceState: Bundle) = {
24 | super.onCreate(savedInstanceState)
25 |
26 | setContentView(layout)
27 |
28 | val color1 = resGetColor(R.color.ripple_bg_color1)
29 | val color2 = resGetColor(R.color.ripple_bg_color2)
30 | val color3 = resGetColor(R.color.ripple_bg_color3)
31 |
32 | runUi(
33 | (rippleBackground <~ vBackgroundColor(color1)) ~
34 | (circle1 <~ cvColor(color1) <~ On.click(anim(circle1, color1))) ~
35 | (circle2 <~ cvColor(color2) <~ On.click(anim(circle2, color2))) ~
36 | (circle3 <~ cvColor(color3) <~ On.click(anim(circle3, color3)))
37 | )
38 |
39 | toolBar foreach setSupportActionBar
40 |
41 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
42 |
43 | }
44 |
45 | override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
46 | case android.R.id.home =>
47 | finish()
48 | false
49 | case _ => super.onOptionsItemSelected(item)
50 | }
51 |
52 | def anim(circleView: Option[CircleView], color: Int): Ui[_] = rippleBackground map {
53 | background ⇒
54 | val rippleData = RippleSnailData.toCenterView(background).
55 | copy(
56 | resColor = color,
57 | onAnimationEnd = Some {
58 | () ⇒
59 | runUi(circleView <~ vTransformation(0, 0))
60 | }
61 | )
62 | (circleView <~~ move(rippleBackground)) ~~ (rippleBackground <~~ ripple(rippleData)) ~~ (circleView <~~ fadeIn(1000))
63 | } getOrElse Ui.nop
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/ripplebackground/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.ripplebackground
2 |
3 | import android.view.Gravity
4 | import android.view.ViewGroup.LayoutParams._
5 | import android.widget.{FrameLayout, LinearLayout}
6 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
7 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
8 | import com.fortysevendeg.macroid.extras.ViewTweaks._
9 | import com.fortysevendeg.scala.android.R
10 | import com.fortysevendeg.scala.android.ui.components.{CircleView, RippleBackgroundView}
11 | import macroid.FullDsl._
12 | import macroid.{ContextWrapper, Tweak}
13 |
14 | import scala.language.postfixOps
15 |
16 | object Styles {
17 |
18 | val rootStyle: Tweak[LinearLayout] =
19 | vMatchParent +
20 | llVertical
21 |
22 | val colorContent: Tweak[FrameLayout] =
23 | llMatchWeightVertical
24 |
25 | def backgroundStyle(implicit context: ContextWrapper): Tweak[RippleBackgroundView] = {
26 | val height = resGetDimensionPixelSize(R.dimen.ripple_bg_height_content)
27 | lp[LinearLayout](MATCH_PARENT, height)
28 | }
29 |
30 | def circlesContentStyle(implicit context: ContextWrapper): Tweak[LinearLayout] = {
31 | vMatchWidth +
32 | llGravity(Gravity.CENTER_HORIZONTAL) +
33 | llHorizontal
34 | }
35 |
36 | def circleStyle(implicit context: ContextWrapper): Tweak[CircleView] = {
37 | val size = resGetDimensionPixelSize(R.dimen.ripple_bg_size_circle)
38 | val margin = resGetDimensionPixelSize(R.dimen.padding_default)
39 | val marginTop = resGetDimensionPixelSize(R.dimen.ripple_bg_height_content)
40 | lp[LinearLayout](size, size) +
41 | vMargin(
42 | marginLeft = margin,
43 | marginTop = margin + marginTop,
44 | marginRight = margin,
45 | marginBottom = margin
46 | )
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/textstyles/Layout.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.textstyles
2 |
3 | import android.widget.{LinearLayout, ScrollView, TextView}
4 | import com.fortysevendeg.scala.android.R
5 | import com.fortysevendeg.macroid.extras.ToolbarTweaks._
6 | import com.fortysevendeg.scala.android.ui.commons.ToolbarLayout
7 | import macroid.FullDsl._
8 | import macroid.ActivityContextWrapper
9 |
10 | trait Layout
11 | extends ToolbarLayout
12 | with Styles {
13 |
14 | def layout(implicit context: ActivityContextWrapper) = {
15 | getUi(
16 | l[LinearLayout](
17 | toolBarLayout <~ tbTitle(R.string.title_text_styles),
18 | l[ScrollView](
19 | l[LinearLayout](
20 | w[TextView] <~ textLargeStyle <~ text("Text Large"),
21 | w[TextView] <~ textMediumStyle <~ text("Text Medium"),
22 | w[TextView] <~ textSmallStyle <~ text("Text Small"),
23 | w[TextView] <~ textLightLargeStyle <~ text("Text Light Large"),
24 | w[TextView] <~ textLightMediumStyle <~ text("Text Light Medium"),
25 | w[TextView] <~ textLightSmallStyle <~ text("Text Light Small"),
26 | w[TextView] <~ textBoldCondensedLargeStyle <~ text("Text Bold Condensed Large"),
27 | w[TextView] <~ textBoldCondensedMediumStyle <~ text("Text Bold Condensed Medium"),
28 | w[TextView] <~ textBoldCondensedSmallStyle <~ text("Text Bold Condensed Small"),
29 | w[TextView] <~ textItalicLargeStyle <~ text("Text Italic Large"),
30 | w[TextView] <~ textItalicMediumStyle <~ text("Text Italic Medium"),
31 | w[TextView] <~ textItalicSmallStyle <~ text("Text Italic Small")
32 | ) <~ scrollStyle
33 | )
34 | ) <~ contentStyle
35 | )
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/textstyles/Styles.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.textstyles
2 |
3 | import com.fortysevendeg.scala.android.R
4 | import com.fortysevendeg.macroid.extras.LinearLayoutTweaks._
5 | import com.fortysevendeg.macroid.extras.ResourcesExtras._
6 | import com.fortysevendeg.macroid.extras.TextTweaks._
7 | import com.fortysevendeg.macroid.extras.ViewTweaks._
8 | import macroid.ContextWrapper
9 | import scala.language.postfixOps
10 |
11 | trait Styles {
12 |
13 | val contentStyle = llVertical
14 |
15 | def scrollStyle(implicit context: ContextWrapper) =
16 | llVertical +
17 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default_large))
18 |
19 | def textLargeStyle(implicit context: ContextWrapper) =
20 | tvSizeResource(R.dimen.font_size_large) +
21 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
22 |
23 | def textMediumStyle(implicit context: ContextWrapper) =
24 | tvSizeResource(R.dimen.font_size_medium) +
25 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
26 |
27 | def textSmallStyle(implicit context: ContextWrapper) =
28 | tvSizeResource(R.dimen.font_size_small) +
29 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
30 |
31 | def textLightLargeStyle(implicit context: ContextWrapper) =
32 | tvSizeResource(R.dimen.font_size_large) +
33 | tvNormalLight +
34 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
35 |
36 | def textLightMediumStyle(implicit context: ContextWrapper) =
37 | tvSizeResource(R.dimen.font_size_medium) +
38 | tvNormalLight +
39 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
40 |
41 | def textLightSmallStyle(implicit context: ContextWrapper) =
42 | tvSizeResource(R.dimen.font_size_small) +
43 | tvNormalLight +
44 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
45 |
46 | def textBoldCondensedLargeStyle(implicit context: ContextWrapper) =
47 | tvSizeResource(R.dimen.font_size_large) +
48 | tvBoldCondensed +
49 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
50 |
51 | def textBoldCondensedMediumStyle(implicit context: ContextWrapper) =
52 | tvSizeResource(R.dimen.font_size_medium) +
53 | tvBoldCondensed +
54 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
55 |
56 | def textBoldCondensedSmallStyle(implicit context: ContextWrapper) =
57 | tvSizeResource(R.dimen.font_size_small) +
58 | tvBoldCondensed +
59 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
60 |
61 | def textItalicLargeStyle(implicit context: ContextWrapper) =
62 | tvSizeResource(R.dimen.font_size_large) +
63 | tvItalic +
64 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
65 |
66 | def textItalicMediumStyle(implicit context: ContextWrapper) =
67 | tvSizeResource(R.dimen.font_size_medium) +
68 | tvItalic +
69 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
70 |
71 | def textItalicSmallStyle(implicit context: ContextWrapper) =
72 | tvSizeResource(R.dimen.font_size_small) +
73 | tvItalic +
74 | vPaddings(resGetDimensionPixelSize(R.dimen.padding_default))
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/scala/com/fortysevendeg/scala/android/ui/textstyles/TextStylesActivity.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.ui.textstyles
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.view.MenuItem
6 | import macroid.Contexts
7 |
8 | class TextStylesActivity
9 | extends AppCompatActivity
10 | with Contexts[AppCompatActivity]
11 | with Layout {
12 |
13 | override def onCreate(savedInstanceState: Bundle) = {
14 | super.onCreate(savedInstanceState)
15 |
16 | setContentView(layout)
17 |
18 | toolBar map setSupportActionBar
19 |
20 | getSupportActionBar.setDisplayHomeAsUpEnabled(true)
21 |
22 | }
23 |
24 | override def onOptionsItemSelected(item: MenuItem): Boolean = {
25 | item.getItemId match {
26 | case android.R.id.home => {
27 | finish()
28 | false
29 | }
30 | }
31 | super.onOptionsItemSelected(item)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/resources/weather.json:
--------------------------------------------------------------------------------
1 | {
2 | "coord": {
3 | "lon": -122.38,
4 | "lat": 47.66
5 | },
6 | "sys": {
7 | "message": 0.0168,
8 | "country": "US",
9 | "sunrise": 1425048757,
10 | "sunset": 1425088297
11 | },
12 | "weather": [
13 | {
14 | "id": 501,
15 | "main": "Rain",
16 | "description": "moderate rain",
17 | "icon": "10d"
18 | }
19 | ],
20 | "base": "cmc stations",
21 | "main": {
22 | "temp": 6.636,
23 | "temp_min": 6.636,
24 | "temp_max": 6.636,
25 | "pressure": 1009.8,
26 | "sea_level": 1021.74,
27 | "grnd_level": 1009.8,
28 | "humidity": 100
29 | },
30 | "wind": {
31 | "speed": 2.17,
32 | "deg": 36.0011
33 | },
34 | "clouds": {
35 | "all": 92
36 | },
37 | "rain": {
38 | "3h": 3.5
39 | },
40 | "dt": 1425060066,
41 | "id": 5809844,
42 | "name": "Seattle",
43 | "cod": 200
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/scala/com/fortysevendeg/scala/android/BaseTestSpecification.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android
2 |
3 | import org.specs2.mutable.Specification
4 | import org.specs2.specification.Scope
5 |
6 | import scala.concurrent.duration.Duration
7 | import scala.concurrent.{Await, Future}
8 |
9 | trait BaseTestSpecification extends Specification {
10 |
11 | def await[T](future: Future[T]) = Await.result(future, Duration.Inf)
12 |
13 | trait BaseTestScope extends Scope
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/scala/com/fortysevendeg/scala/android/ContextWrapperContextTestSupport.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android
2 |
3 | import android.content.Context
4 | import macroid.ContextWrapper
5 | import org.specs2.mock.Mockito
6 |
7 | trait ContextWrapperContextTestSupport
8 | extends Mockito {
9 |
10 | val mockContext = mock[Context]
11 |
12 | implicit val context: ContextWrapper = mock[ContextWrapper]
13 |
14 | context.application returns mockContext
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/scala/com/fortysevendeg/scala/android/modules/forecast/ForecastServicesSpec.scala:
--------------------------------------------------------------------------------
1 | package com.fortysevendeg.scala.android.modules.forecast
2 |
3 | import com.fortysevendeg.scala.android.modules.forecast.impl.ForecastServices
4 | import com.fortysevendeg.scala.android.modules.forecast.model._
5 | import com.fortysevendeg.scala.android.{ContextWrapperContextTestSupport, BaseTestSpecification}
6 | import com.squareup.okhttp.OkHttpClient
7 | import io.taig.communicator.result.Parser
8 | import macroid.ContextWrapper
9 |
10 | import scala.concurrent.duration.Duration
11 | import scala.concurrent.{Await, Future}
12 |
13 | trait ForecastServicesSpecification
14 | extends BaseTestSpecification
15 | with ContextWrapperContextTestSupport {
16 |
17 | trait ForecastServicesScope
18 | extends BaseTestScope
19 | with ForecastServices
20 | with ForecastServicesData {
21 |
22 | override def loadJsonUrl(latitude: Double, longitude: Double)(implicit context: ContextWrapper): String = "http://fake_url/"
23 |
24 | override def loadHeaderTuple(implicit context: ContextWrapper): (String, String) = ("key_name", "key_value")
25 |
26 | }
27 |
28 | }
29 |
30 | trait ForecastServicesData {
31 |
32 | val validJson: ApiModel = ApiModel(
33 | id = 5809844,
34 | dt = 1425060066,
35 | base = "cmc stations",
36 | cod = 200,
37 | name = "Seattle",
38 | coord = ApiCoord(-122.38, 47.66),
39 | sys = ApiSys(
40 | message = Some(0.0168),
41 | country = Some("US"),
42 | sunrise = Some(1425048757),
43 | sunset = Some(1425088297)),
44 | main = ApiMain(
45 | temp = Some(6.636),
46 | temp_min = Some(6.636),
47 | temp_max = Some(6.636),
48 | pressure = Some(1009.8),
49 | sea_level = Some(1021.71),
50 | grnd_level = Some(1009.8),
51 | humidity = Some(100)),
52 | weather = Seq(ApiWeather(id = 501, main = "Rain", description = "moderate rain", icon = "10d")),
53 | wind = ApiWind(speed = Some(2.17), deg = Some(36.0011)),
54 | rain = Some(Map("3h" -> 3.5)),
55 | clouds = ApiClouds(all = Some(92)))
56 |
57 | }
58 |
59 | class ForecastServicesSpec extends ForecastServicesSpecification {
60 |
61 | "ForecastServices component" should {
62 |
63 | "return forecast with right JSON" in
64 | new ForecastServicesScope {
65 |
66 | override def loadJson[T](url: String, headers: Seq[(String, String)])(implicit parser: Parser[T], client: OkHttpClient = new OkHttpClient()): Future[T] =
67 | Future.successful[T](validJson.asInstanceOf[T])
68 |
69 | val forecast = Forecast(
70 | Location(5809844, "Seattle", 47.66, -122.38),
71 | Some(Weather(501, "Rain", "moderate rain", "10d", Some(6.636))))
72 |
73 | Await.result(loadForecast(ForecastRequest(0, 0)), Duration.Inf) shouldEqual ForecastResponse(Some(forecast))
74 | }
75 |
76 | "got the exception thrown by the call to loadJson" in
77 | new ForecastServicesScope {
78 |
79 | override def loadJson[T](url: String, headers: Seq[(String, String)])(implicit parser: Parser[T], client: OkHttpClient = new OkHttpClient()): Future[T] =
80 | Future.failed[T](new RuntimeException())
81 |
82 | Await.result(loadForecast(ForecastRequest(0, 0)), Duration.Inf) must throwA[RuntimeException]
83 | }
84 |
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/src/test/scala/com/fortysevendeg/scala/android/modules/forecast/model/JsonModelSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * 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 |
17 | package com.fortysevendeg.scala.android.modules.forecast.model
18 |
19 | import com.fortysevendeg.scala.android.BaseTestSpecification
20 | import play.api.libs.json.{JsValue, Json}
21 | import JsonImplicits._
22 |
23 | object JsonImplicits {
24 |
25 | implicit val apiCloudsReads = Json.reads[ApiClouds]
26 | implicit val apiWindReads = Json.reads[ApiWind]
27 | implicit val apiWeatherReads = Json.reads[ApiWeather]
28 | implicit val apiMainReads = Json.reads[ApiMain]
29 | implicit val apiSysReads = Json.reads[ApiSys]
30 | implicit val apiCoordReads = Json.reads[ApiCoord]
31 | implicit val apiModelReads = Json.reads[ApiModel]
32 |
33 | }
34 |
35 | class JsonModelSpec
36 | extends BaseTestSpecification {
37 |
38 | "load and map sample json" should {
39 |
40 | "return an ApiModel class with the right fields" in new BaseTestScope {
41 |
42 |
43 | val jsonSource = scala.io.Source.fromInputStream(JsonImplicits.getClass.getResourceAsStream("/weather.json")).mkString
44 | val json: JsValue = Json.parse(jsonSource)
45 | val jsonValue = json.as[ApiModel]
46 |
47 | jsonValue.weather.size shouldEqual 1
48 | jsonValue.name shouldEqual "Seattle"
49 | }
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------