Skip to content

Commit 8a5844c

Browse files
authored
Feature/4.1 widget (#150)
* Add basic widget * Add widget assets * Clearing out browser UI manually - this shouldn't need done in code Ideally, we would get a new Activity instance and not have to handle this in code. * Fix padding issue on widget * Update preview image and fix padding * Fix test * Remove unused resource file * Add FLAG_ACTIVITY_CLEAR_TOP to resolve stack issue launching from widget * Keep the `HomeActivity` around even when launching from widget * Removing flag as it's no longer needed now that we're not killing home * Fix formatting * Restrict min widget width to 4 columns * Use 4 columns as default widget width, allow user to drop down to 3
1 parent 7b1535e commit 8a5844c

File tree

15 files changed

+160
-25
lines changed

15 files changed

+160
-25
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserViewModelTest.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@ import android.arch.persistence.room.Room
2424
import android.net.Uri
2525
import android.support.test.InstrumentationRegistry
2626
import com.duckduckgo.app.autocomplete.api.AutoCompleteApi
27-
import com.duckduckgo.app.blockingObserve
2827
import com.duckduckgo.app.bookmarks.db.BookmarkEntity
2928
import com.duckduckgo.app.bookmarks.db.BookmarksDao
30-
import com.duckduckgo.app.bookmarks.ui.BookmarksActivity
3129
import com.duckduckgo.app.browser.BrowserViewModel.Command
3230
import com.duckduckgo.app.browser.BrowserViewModel.Command.LandingPage
3331
import com.duckduckgo.app.browser.BrowserViewModel.Command.Navigate
3432
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
35-
import com.duckduckgo.app.global.StringResolver
3633
import com.duckduckgo.app.global.db.AppDatabase
3734
import com.duckduckgo.app.privacymonitor.db.NetworkLeaderboardDao
3835
import com.duckduckgo.app.privacymonitor.db.NetworkLeaderboardEntry
@@ -46,10 +43,7 @@ import com.duckduckgo.app.settings.db.SettingsDataStore
4643
import com.duckduckgo.app.trackerdetection.model.TrackerNetwork
4744
import com.duckduckgo.app.trackerdetection.model.TrackerNetworks
4845
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
49-
import com.nhaarman.mockito_kotlin.any
50-
import com.nhaarman.mockito_kotlin.doReturn
51-
import com.nhaarman.mockito_kotlin.mock
52-
import com.nhaarman.mockito_kotlin.whenever
46+
import com.nhaarman.mockito_kotlin.*
5347
import org.junit.After
5448
import org.junit.Assert.*
5549
import org.junit.Before
@@ -249,7 +243,7 @@ class BrowserViewModelTest {
249243
fun whenSharedTextReceivedThenNavigationTriggered() {
250244
testee.onSharedTextReceived("http://example.com")
251245
val captor: ArgumentCaptor<Command> = ArgumentCaptor.forClass(Command::class.java)
252-
verify(mockNavigationObserver).onChanged(captor.capture())
246+
verify(mockNavigationObserver, times(2)).onChanged(captor.capture())
253247
assertNotNull(captor.value)
254248
assertTrue(captor.value is Navigate)
255249
}

app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
android:supportsRtl="true"
1616
android:theme="@style/AppTheme"
1717
tools:ignore="GoogleAppIndexingWarning">
18-
1918
<meta-data
2019
android:name="android.webkit.WebView.MetricsOptOut"
2120
android:value="true" />
@@ -25,10 +24,10 @@
2524
android:label="@string/appName">
2625
<intent-filter>
2726
<action android:name="android.intent.action.MAIN" />
27+
2828
<category android:name="android.intent.category.LAUNCHER" />
2929
</intent-filter>
3030
</activity>
31-
3231
<activity
3332
android:name="com.duckduckgo.app.home.HomeActivity"
3433
android:label="@string/appName"
@@ -48,47 +47,41 @@
4847
<!-- Allows apps to consume links and text shared from other apps e.g chrome -->
4948
<intent-filter>
5049
<action android:name="android.intent.action.SEND" />
50+
5151
<category android:name="android.intent.category.DEFAULT" />
52+
5253
<data android:mimeType="text/plain" />
5354
</intent-filter>
54-
5555
</activity>
56-
5756
<activity
5857
android:name=".BrowserActivity"
5958
android:configChanges="keyboardHidden|orientation|screenSize"
6059
android:parentActivityName="com.duckduckgo.app.home.HomeActivity" />
61-
6260
<activity
6361
android:name="com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity"
6462
android:label="@string/privacyDashboardActivityTitle"
6563
android:parentActivityName=".BrowserActivity" />
66-
6764
<activity
6865
android:name="com.duckduckgo.app.privacymonitor.ui.ScorecardActivity"
6966
android:label="@string/scorecardActivityTitle"
7067
android:parentActivityName="com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity" />
71-
7268
<activity
7369
android:name="com.duckduckgo.app.privacymonitor.ui.TrackerNetworksActivity"
7470
android:label="@string/networksActivityTitle"
7571
android:parentActivityName="com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity" />
76-
7772
<activity
7873
android:name="com.duckduckgo.app.privacymonitor.ui.PrivacyPracticesActivity"
7974
android:label="@string/privacyTermsActivityTitle"
8075
android:parentActivityName="com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity" />
81-
8276
<activity
8377
android:name="com.duckduckgo.app.settings.SettingsActivity"
84-
android:label="@string/settingsActivityTitle" />
78+
android:label="@string/settingsActivityTitle"/>
8579

8680
<activity
8781
android:name="com.duckduckgo.app.about.AboutDuckDuckGoActivity"
8882
android:label="@string/aboutActivityTitle"
8983
android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity"
9084
android:theme="@style/AppTheme" />
91-
9285
<activity
9386
android:name="com.duckduckgo.app.bookmarks.ui.BookmarksActivity"
9487
android:label="@string/bookmarksActivityTitle" />
@@ -97,7 +90,15 @@
9790
android:name="com.duckduckgo.app.job.AppConfigurationJobService"
9891
android:permission="android.permission.BIND_JOB_SERVICE" />
9992

93+
<receiver android:name="com.duckduckgo.widget.SearchWidget">
94+
<intent-filter>
95+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
96+
</intent-filter>
10097

98+
<meta-data
99+
android:name="android.appwidget.provider"
100+
android:resource="@xml/search_widget_info" />
101+
</receiver>
101102
</application>
102103

103104
</manifest>

app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ class BrowserActivity : DuckDuckGoActivity() {
148148
val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:${it.telephoneNumber}"))
149149
startActivity(intent)
150150
}
151+
is BrowserViewModel.Command.ShowKeyboard -> {
152+
Timber.i("Command: showing keyboard")
153+
omnibarTextInput.postDelayed({omnibarTextInput.showKeyboard()}, 300)
154+
}
155+
is BrowserViewModel.Command.ReinitialiseWebView -> {
156+
webView.clearHistory()
157+
}
151158
}
152159
})
153160
}
@@ -287,7 +294,7 @@ class BrowserActivity : DuckDuckGoActivity() {
287294
setSupportZoom(true)
288295
}
289296

290-
webView.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->
297+
webView.setDownloadListener { url, _, _, _, _ ->
291298
val request = DownloadManager.Request(Uri.parse(url))
292299
request.allowScanningByMediaScanner()
293300
request.setNotificationVisibility(VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
@@ -378,31 +385,37 @@ class BrowserActivity : DuckDuckGoActivity() {
378385
popupMenu.show(rootView, anchorView)
379386
}
380387

388+
@Suppress("UNUSED_PARAMETER")
381389
fun onGoForwardClicked(view: View) {
382390
webView.goForward()
383391
popupMenu.dismiss()
384392
}
385393

394+
@Suppress("UNUSED_PARAMETER")
386395
fun onGoBackClicked(view: View) {
387396
webView.goBack()
388397
popupMenu.dismiss()
389398
}
390399

400+
@Suppress("UNUSED_PARAMETER")
391401
fun onRefreshClicked(view: View) {
392402
webView.reload()
393403
popupMenu.dismiss()
394404
}
395405

406+
@Suppress("UNUSED_PARAMETER")
396407
fun onBookmarksClicked(view: View) {
397408
launchBookmarksView()
398409
popupMenu.dismiss()
399410
}
400411

412+
@Suppress("UNUSED_PARAMETER")
401413
fun onAddBookmarkClicked(view: View) {
402414
addBookmark()
403415
popupMenu.dismiss()
404416
}
405417

418+
@Suppress("UNUSED_PARAMETER")
406419
fun onSettingsClicked(view: View) {
407420
launchSettingsView()
408421
popupMenu.dismiss()

app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import com.duckduckgo.app.bookmarks.db.BookmarksDao
3030
import com.duckduckgo.app.browser.BrowserViewModel.Command.Navigate
3131
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
3232
import com.duckduckgo.app.global.SingleLiveEvent
33-
import com.duckduckgo.app.global.StringResolver
3433
import com.duckduckgo.app.privacymonitor.SiteMonitor
3534
import com.duckduckgo.app.privacymonitor.db.NetworkLeaderboardDao
3635
import com.duckduckgo.app.privacymonitor.db.NetworkLeaderboardEntry
@@ -84,6 +83,8 @@ class BrowserViewModel(
8483
class DialNumber(val telephoneNumber: String) : Command()
8584
class SendSms(val telephoneNumber: String) : Command()
8685
class SendEmail(val emailAddress: String) : Command()
86+
class ShowKeyboard : Command()
87+
class ReinitialiseWebView : Command()
8788
}
8889

8990
/* Observable data for Activity to subscribe to */
@@ -108,6 +109,7 @@ class BrowserViewModel(
108109
private var appConfigurationDownloaded = false
109110

110111
init {
112+
command.value = Command.ShowKeyboard()
111113
viewState.value = ViewState(canAddBookmarks = false)
112114
privacyMonitorRepository.privacyMonitor = MutableLiveData()
113115
appConfigurationObservable.observeForever(appConfigurationObserver)
@@ -148,6 +150,7 @@ class BrowserViewModel(
148150
if (input.isBlank()) {
149151
return
150152
}
153+
151154
val trimmedInput = input.trim()
152155
url.value = buildUrl(trimmedInput)
153156
viewState.value = currentViewState().copy(

app/src/main/java/com/duckduckgo/app/home/HomeActivity.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ class HomeActivity : DuckDuckGoActivity() {
7878
}
7979

8080
private fun consumeSharedQuery(intent: Intent?) {
81-
val query = intent?.intentText ?: return
81+
82+
if (intent == null) return
83+
84+
if (intent.hasExtra(KEY_SKIP_HOME)) {
85+
startActivity(BrowserActivity.intent(this))
86+
return
87+
}
88+
89+
val query = intent.intentText ?: return
8290
startActivity(BrowserActivity.intent(this, query))
8391
}
8492

@@ -143,5 +151,13 @@ class HomeActivity : DuckDuckGoActivity() {
143151
}
144152
return intent
145153
}
154+
155+
fun launchSkipHome(context: Context) : Intent {
156+
val intent = intent(context)
157+
intent.putExtra(KEY_SKIP_HOME, true)
158+
return intent
159+
}
160+
161+
const val KEY_SKIP_HOME: String = "KEY_SKIP_HOME"
146162
}
147163
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2018 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.widget
18+
19+
import android.app.PendingIntent
20+
import android.appwidget.AppWidgetManager
21+
import android.appwidget.AppWidgetProvider
22+
import android.content.Context
23+
import android.widget.RemoteViews
24+
import com.duckduckgo.app.browser.R
25+
import com.duckduckgo.app.home.HomeActivity
26+
27+
/**
28+
* Implementation of App Widget functionality.
29+
*/
30+
class SearchWidget : AppWidgetProvider() {
31+
32+
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
33+
// There may be multiple widgets active, so update all of them
34+
for (appWidgetId in appWidgetIds) {
35+
updateAppWidget(context, appWidgetManager, appWidgetId)
36+
}
37+
}
38+
39+
companion object {
40+
41+
internal fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager,
42+
appWidgetId: Int) {
43+
44+
val views = RemoteViews(context.packageName, R.layout.search_widget)
45+
views.setOnClickPendingIntent(R.id.widgetContainer, buildPendingIntent(context))
46+
appWidgetManager.updateAppWidget(appWidgetId, views)
47+
}
48+
49+
private fun buildPendingIntent(context: Context) : PendingIntent {
50+
val intent = HomeActivity.launchSkipHome(context)
51+
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
52+
}
53+
}
54+
}
55+
5.06 KB
Loading
2.98 KB
Loading
17.8 KB
Loading
7.31 KB
Loading

0 commit comments

Comments
 (0)