diff --git a/README.md b/README.md index c318038..afff2a7 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Scala on Android ============== This application is written entirely in Scala on Android. We are excited to make the application open source and share the code with you. We have used the [macroid](http://macroid.github.io/) library extensively in this project. In addition we have contributed our own Macroid extenions to this application, that can be found here: [macroid-extras](http://macroid.github.io/). + Compile ====== @@ -27,7 +28,8 @@ $ ./activator > run ``` -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. +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. + Download ======== diff --git a/build.sbt b/build.sbt index b997978..f17d380 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,8 @@ import Libraries.android._ import Libraries.macroid._ +import Libraries.playServices._ import Libraries.json._ +import android.PromptPasswordsSigningConfig android.Plugin.androidBuild @@ -27,14 +29,21 @@ resolvers ++= Settings.resolvers libraryDependencies ++= Seq( aar(macroidRoot), aar(androidAppCompat), - aar(androidCardView), - aar(androidRecyclerview), aar(macroidExtras), + aar(playServicesBase), json4s, compilerPlugin(Libraries.wartRemover)) + +apkSigningConfig in Android := Option( + PromptPasswordsSigningConfig( + keystore = new File(Path.userHome.absolutePath + "/.android/translate-bubble.keystore"), + alias = "47deg")) + run <<= run in Android +packageRelease <<= packageRelease in Android + proguardScala in Android := true useProguard in Android := true diff --git a/proguard-sbt.txt b/proguard-sbt.txt index b7d14bb..6c92710 100644 --- a/proguard-sbt.txt +++ b/proguard-sbt.txt @@ -166,9 +166,6 @@ # view res/layout/abc_search_view.xml #generated:78 -keep class android.support.v7.widget.SearchView$SearchAutoComplete { (...); } -# view AndroidManifest.xml #generated:26 --keep class android.support.v7.widget.TestActivity { (...); } - # view res/layout/abc_screen_toolbar.xml #generated:36 -keep class android.support.v7.widget.Toolbar { (...); } @@ -184,11 +181,11 @@ # view AndroidManifest.xml #generated:16 -keep class com.fortysevendeg.translatebubble.ui.bubbleservice.BubbleService { (...); } -# view res/layout/actions_view.xml #generated:21 +# view res/layout/actions_view.xml #generated:37 -keep class com.fortysevendeg.translatebubble.ui.components.CloseView { (...); } -# view res/layout/actions_view.xml #generated:11 -# view res/layout/actions_view.xml #generated:15 +# view res/layout/actions_view.xml #generated:27 +# view res/layout/actions_view.xml #generated:31 -keep class com.fortysevendeg.translatebubble.ui.components.DisableView { (...); } # view AndroidManifest.xml #generated:14 diff --git a/project/Libraries.scala b/project/Libraries.scala index 7f535f6..caef8fc 100755 --- a/project/Libraries.scala +++ b/project/Libraries.scala @@ -25,6 +25,27 @@ object Libraries { lazy val androidCardView = androidDep("cardview-v7") } + object playServices { + + def playServicesDep(module: String) = "com.google.android.gms" % module % Versions.playServicesV + + lazy val playServicesGooglePlus = playServicesDep("play-services-plus") + lazy val playServicesAccountLogin = playServicesDep("play-services-identity") + lazy val playServicesActivityRecognition = playServicesDep("play-services-location") + lazy val playServicesAppIndexing = playServicesDep("play-services-appindexing") + lazy val playServicesCast = playServicesDep("play-services-cast") + lazy val playServicesDrive = playServicesDep("play-services-drive") + lazy val playServicesFit = playServicesDep("play-services-fitness") + lazy val playServicesMaps = playServicesDep("play-services-maps") + lazy val playServicesAds = playServicesDep("play-services-ads") + lazy val playServicesPanoramaViewer = playServicesDep("play-services-panorama") + lazy val playServicesGames = playServicesDep("play-services-games") + lazy val playServicesWallet = playServicesDep("play-services-wallet") + lazy val playServicesWear = playServicesDep("play-services-wearable") + // Google Actions, Google Analytics and Google Cloud Messaging + lazy val playServicesBase = playServicesDep("play-services-base") + } + object macroid { def macroid(module: String = "") = diff --git a/project/Versions.scala b/project/Versions.scala index 0828d91..3e4e4f6 100755 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -9,4 +9,5 @@ object Versions { val macroidV = "2.0.0-M3" val wartremoverV = "0.11" val json4s = "3.2.10" + val playServicesV = "6.5.87" } \ No newline at end of file diff --git a/src/main/res/xml/app_tracker.xml b/src/main/res/xml/app_tracker.xml new file mode 100644 index 0000000..7b301a7 --- /dev/null +++ b/src/main/res/xml/app_tracker.xml @@ -0,0 +1,26 @@ + + + + + + Translate Bubble + + false + + UA-58651614-1 + + \ No newline at end of file diff --git a/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistry.scala b/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistry.scala index 3a40241..9b0c922 100644 --- a/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistry.scala +++ b/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistry.scala @@ -16,6 +16,7 @@ package com.fortysevendeg.translatebubble.modules +import com.fortysevendeg.translatebubble.modules.analytics.AnalyticsServicesComponent import com.fortysevendeg.translatebubble.modules.clipboard.{ClipboardServicesComponent, ClipboardServices} import com.fortysevendeg.translatebubble.modules.notifications.{NotificationsServicesComponent, NotificationsServices} import com.fortysevendeg.translatebubble.modules.persistent.{PersistentServicesComponent, PersistentServices} @@ -26,3 +27,4 @@ trait ComponentRegistry with PersistentServicesComponent with TranslateServicesComponent with NotificationsServicesComponent + with AnalyticsServicesComponent diff --git a/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistryImpl.scala b/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistryImpl.scala index d649a9e..7a21bf1 100644 --- a/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistryImpl.scala +++ b/src/main/scala/com/fortysevendeg/translatebubble/modules/ComponentRegistryImpl.scala @@ -17,6 +17,7 @@ package com.fortysevendeg.translatebubble.modules import com.fortysevendeg.macroid.extras.AppContextProvider +import com.fortysevendeg.translatebubble.modules.analytics.impl.AnalyticsServicesComponentImpl import com.fortysevendeg.translatebubble.modules.clipboard.impl.ClipboardServicesComponentImpl import com.fortysevendeg.translatebubble.modules.notifications.impl.NotificationsServicesComponentImpl import com.fortysevendeg.translatebubble.modules.persistent.impl.PersistentServicesComponentImpl @@ -29,3 +30,4 @@ trait ComponentRegistryImpl with PersistentServicesComponentImpl with TranslateServicesComponentImpl with NotificationsServicesComponentImpl + with AnalyticsServicesComponentImpl diff --git a/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/AnalyticsServicesComponent.scala b/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/AnalyticsServicesComponent.scala new file mode 100644 index 0000000..4ff27b9 --- /dev/null +++ b/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/AnalyticsServicesComponent.scala @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fortysevendeg.translatebubble.modules.analytics + +trait AnalyticsServices { + def send(screenName: String, + category: Option[String] = None, + action: Option[String] = None, + label: Option[String] = None): Unit +} + +trait AnalyticsServicesComponent { + val analyticsServices: AnalyticsServices +} diff --git a/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/impl/AnalyticsServicesComponentImpl.scala b/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/impl/AnalyticsServicesComponentImpl.scala new file mode 100644 index 0000000..30b0f8b --- /dev/null +++ b/src/main/scala/com/fortysevendeg/translatebubble/modules/analytics/impl/AnalyticsServicesComponentImpl.scala @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fortysevendeg.translatebubble.modules.analytics.impl + +import com.fortysevendeg.macroid.extras.AppContextProvider +import com.fortysevendeg.translatebubble.R +import com.fortysevendeg.translatebubble.modules.analytics.{AnalyticsServices, AnalyticsServicesComponent} +import com.google.android.gms.analytics.{HitBuilders, GoogleAnalytics} + +trait AnalyticsServicesComponentImpl + extends AnalyticsServicesComponent { + + self: AppContextProvider => + + lazy val analyticsServices = new AnalyticsServicesImpl + + class AnalyticsServicesImpl + extends AnalyticsServices { + + lazy val tracker = { + val googleAnalytics = GoogleAnalytics.getInstance(appContextProvider.get) + googleAnalytics.newTracker(R.xml.app_tracker) + } + + def send(screenName: String, + category: Option[String] = None, + action: Option[String] = None, + label: Option[String] = None): Unit = { + tracker.setScreenName(screenName) + val event = new HitBuilders.EventBuilder() + category map (event.setCategory(_)) + action map (event.setAction(_)) + label map (event.setLabel(_)) + tracker.send(event.build()) + } + + } + +} diff --git a/src/main/scala/com/fortysevendeg/translatebubble/ui/bubbleservice/BubbleService.scala b/src/main/scala/com/fortysevendeg/translatebubble/ui/bubbleservice/BubbleService.scala index f8cf952..1099b35 100644 --- a/src/main/scala/com/fortysevendeg/translatebubble/ui/bubbleservice/BubbleService.scala +++ b/src/main/scala/com/fortysevendeg/translatebubble/ui/bubbleservice/BubbleService.scala @@ -37,6 +37,7 @@ import com.fortysevendeg.translatebubble.ui.components.{ActionsView, BubbleView, import com.fortysevendeg.translatebubble.utils.TranslateUIType import macroid.FullDsl._ import macroid.{AppContext, Ui} +import com.fortysevendeg.translatebubble.ui.commons.Strings._ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Failure, Try} @@ -99,10 +100,16 @@ class BubbleService bubble.hideFromCloseAction(paramsBubble, windowManager) // Bubble was moved over DisableTranslation case actionsView if (actionsView.isOverDisableView(x, y)) => + analyticsServices.send( + analyticsTranslateService, + Some(analyticsDisable)) persistentServices.disableTranslation() bubble.hideFromOptionAction(paramsBubble, windowManager) // Bubble was moved over DisableTranslation during 30 minutes case actionsView if (actionsView.isOver30minView(x, y)) => + analyticsServices.send( + analyticsTranslateService, + Some(analytics30MinDisable)) persistentServices.disable30MinutesTranslation() bubble.hideFromOptionAction(paramsBubble, windowManager) // Bubble drops somewhere else @@ -355,11 +362,8 @@ class BubbleService private def onStartTranslate() { val typeTranslateUI = persistentServices.getTypeTranslateUI() if (typeTranslateUI == TranslateUIType.BUBBLE) { - if (bubbleStatus == BubbleStatus.FLOATING) { - bubble.show(paramsBubble, windowManager) - } else { - contentView.setTexts(getString(R.string.translating), "", "") - } + bubble.show(paramsBubble, windowManager) + contentView.setTexts(getString(R.string.translating), "", "") } val result = for { @@ -368,40 +372,49 @@ class BubbleService translateResponse <- translateServices.translate( TranslateRequest(text = textResponse.text, from = persistentResponse.from, to = persistentResponse.to) ) - } yield (textResponse.text, translateResponse.translated) - result.mapUi(texts => onEndTranslate(texts._1, texts._2)).recover { + } yield (textResponse.text, translateResponse.translated, + "%s-%s".format(persistentResponse.from.toString, persistentResponse.to.toString)) + result.mapUi(texts => onEndTranslate(texts._1, texts._2, texts._3)).recover { case _ => translatedFailed() } } - private def onEndTranslate(maybeOriginalText: Option[String], maybeTranslatedText: Option[String]) = { + private def onEndTranslate( + maybeOriginalText: Option[String], + maybeTranslatedText: Option[String], + label: String) = { val typeTranslateUI = persistentServices.getTypeTranslateUI() + + analyticsServices.send( + analyticsTranslateService, + Some(typeTranslateUI.toString), + Some(analyticsClipboard), + Some(label)) + for { originalText <- maybeOriginalText translatedText <- maybeTranslatedText languages <- persistentServices.getLanguagesString } yield { - if (typeTranslateUI == TranslateUIType.BUBBLE) { - contentView.setTexts(languages, originalText, translatedText) - if (bubbleStatus == BubbleStatus.FLOATING) { + typeTranslateUI match { + case TranslateUIType.BUBBLE => + contentView.setTexts(languages, originalText, translatedText) bubble.stopAnimation() - } - } else if (typeTranslateUI == TranslateUIType.NOTIFICATION) { - notificationsServices.showTextTranslated(ShowTextTranslatedRequest(originalText, translatedText)) + case TranslateUIType.NOTIFICATION => + notificationsServices.showTextTranslated(ShowTextTranslatedRequest(originalText, translatedText)) } } } private def translatedFailed() { val typeTranslateUI = persistentServices.getTypeTranslateUI() - if (typeTranslateUI == TranslateUIType.BUBBLE) { - contentView.setTexts(getString(R.string.failedTitle), getString(R.string.failedMessage), "") - if (bubbleStatus == BubbleStatus.FLOATING) { + typeTranslateUI match { + case TranslateUIType.BUBBLE => + contentView.setTexts(getString(R.string.failedTitle), getString(R.string.failedMessage), "") bubble.stopAnimation() - } - } else if (typeTranslateUI == TranslateUIType.NOTIFICATION) { - notificationsServices.failed() + case TranslateUIType.NOTIFICATION => + notificationsServices.failed() } } diff --git a/src/main/scala/com/fortysevendeg/translatebubble/ui/commons/Strings.scala b/src/main/scala/com/fortysevendeg/translatebubble/ui/commons/Strings.scala new file mode 100644 index 0000000..b3310a7 --- /dev/null +++ b/src/main/scala/com/fortysevendeg/translatebubble/ui/commons/Strings.scala @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 47 Degrees, LLC http://47deg.com hello@47deg.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fortysevendeg.translatebubble.ui.commons + +object Strings { + + val analyticsPreferencesScreen = "Preferences Screen" + + val analyticsWizardScreen = "Wizard Screen" + + val analyticsOpenSourceDialog = "Open Source Dialog" + + val analyticsGoToGitHub = "Go To GitHub" + + val analyticsTutorialScreen = "Tutorial Screen" + + val analyticsTranslateService = "Translate Service" + + val analyticsClipboard = "Clipboard" + + val analyticsDisable = "Disable from Bubble" + + val analytics30MinDisable = "Disable 30 minutes from Bubble" + +} diff --git a/src/main/scala/com/fortysevendeg/translatebubble/ui/preferences/MainActivity.scala b/src/main/scala/com/fortysevendeg/translatebubble/ui/preferences/MainActivity.scala index e66ea6e..22d02c8 100644 --- a/src/main/scala/com/fortysevendeg/translatebubble/ui/preferences/MainActivity.scala +++ b/src/main/scala/com/fortysevendeg/translatebubble/ui/preferences/MainActivity.scala @@ -21,7 +21,6 @@ import android.content.{Intent, DialogInterface} import android.os.Bundle import android.preference.Preference.{OnPreferenceChangeListener, OnPreferenceClickListener} import android.preference._ -import android.widget.Toast import com.fortysevendeg.translatebubble.R import com.fortysevendeg.macroid.extras.PreferencesBuildingExtra._ import com.fortysevendeg.macroid.extras.{AppContextProvider, RootPreferencesFragment} @@ -29,6 +28,7 @@ import com.fortysevendeg.translatebubble.modules.ComponentRegistryImpl import com.fortysevendeg.translatebubble.modules.clipboard.CopyToClipboardRequest import com.fortysevendeg.translatebubble.modules.persistent.GetLanguagesRequest import com.fortysevendeg.translatebubble.ui.bubbleservice.BubbleService +import com.fortysevendeg.translatebubble.ui.commons.Strings._ import com.fortysevendeg.translatebubble.ui.wizard.WizardActivity import com.fortysevendeg.translatebubble.utils.{TranslateUIType, LanguageType} import macroid.FullDsl._ @@ -71,6 +71,8 @@ class DefaultPreferencesFragment super.onCreate(savedInstanceState) + analyticsServices.send(analyticsPreferencesScreen) + // TODO Don't use 'map'. We should create a Tweak when MacroidExtra module works launchFake map ( @@ -98,11 +100,13 @@ class DefaultPreferencesFragment openSource map ( _.setOnPreferenceClickListener(new OnPreferenceClickListener { override def onPreferenceClick(preference: Preference): Boolean = { + analyticsServices.send(analyticsOpenSourceDialog) val builder = new AlertDialog.Builder(getActivity) builder.setMessage(R.string.openSourceMessage) .setPositiveButton(R.string.goToGitHub, new DialogInterface.OnClickListener() { def onClick(dialog: DialogInterface, id: Int) { // TODO Open github website project + analyticsServices.send(analyticsGoToGitHub) dialog.dismiss() } }) diff --git a/src/main/scala/com/fortysevendeg/translatebubble/ui/wizard/WizardActivity.scala b/src/main/scala/com/fortysevendeg/translatebubble/ui/wizard/WizardActivity.scala index 951b5c7..be2b35f 100644 --- a/src/main/scala/com/fortysevendeg/translatebubble/ui/wizard/WizardActivity.scala +++ b/src/main/scala/com/fortysevendeg/translatebubble/ui/wizard/WizardActivity.scala @@ -27,6 +27,7 @@ import com.fortysevendeg.translatebubble.modules.ComponentRegistryImpl import com.fortysevendeg.translatebubble.ui.preferences.MainActivity import macroid.FullDsl._ import macroid.{AppContext, Contexts, Transformer} +import com.fortysevendeg.translatebubble.ui.commons.Strings._ import scala.concurrent.ExecutionContext.Implicits.global @@ -46,6 +47,9 @@ class WizardActivity startActivity(new Intent(this, classOf[MainActivity])) finish() } + + analyticsServices.send(if (modeTutorial) analyticsTutorialScreen else analyticsWizardScreen) + setContentView(layout) val steps = Steps.steps.length