lambdasoup / watchlater Goto Github PK
View Code? Open in Web Editor NEWAndroid app for adding videos to your Watch Later list
Home Page: https://github.com/lambdasoup/watchlater
License: GNU General Public License v3.0
Android app for adding videos to your Watch Later list
Home Page: https://github.com/lambdasoup/watchlater
License: GNU General Public License v3.0
Stopgap: allow stateloss
Proper solution: redesign execution of business code (see also #28)
We want a launcher activity to deconfuse users who installed the app and do not realize how it is supposed to be used. Explanatory text and some diagnosis and advice on configuring defaults/app link settings appropriately.
This is an appropriate settings activity, reachable via the options menu from the AddActivity.
Display and reset the default youtube account (see #23, do setting in the account chooser with "always use this account" checkbox).
Observe: use watchlater to add some video. After success, change orientation, and observe how the whole process starts anew, culminating in video_already_in_playlist, obviously.
Seems to be fixed. The observable rescubscription after permission result should now be unnecessary.
Re-evalutate the situation regarding multiple/default YouTube channels
The app does not show all playlists in case the user has more than 50 playlists. The reason is that the app requests only max 50 playlists (hardcoded constant).
Quick fix would be to increase the value eg to 100. This obviously would not solve the problem per se but would be quick and somewhat safe.
Proper fix would be some sort of paging mechanism which involves much more code and maybe even some sorting or searching of playlists.
isDestroyed() does not exist yet.
To start off, great app, thanks!
I do have 4 accounts on my phone, but use only one to watch YouTube. Currently it asks everytime which account to use to send the video.
It would be great to be able to set a default account.
Behaviour should be as "back" (finish activity). A close icon (cross) would be ideal, left-arrow is also acceptable. Parent activity declaration is less than ideal, and it is unclear how to test on low API levels.
User request: Wants to set Watchlater as default; allow opening item with youtube via button or something (after always adding it to the watchlater list, if applicable?)
YouTube links from email sharing have a link that is not matching by our Intent Filters.
Example (removed possibly personalized token)
http://www.youtube.com/attribution_link?a={...}&u=/watch%3Fv%3DJ1zNbWJC5aw%26feature%3Dem-subs_digest
Probably best done after refactoring all the screens and their behaviour into fragments.
Vimeo support should be added
android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'is displayed on the screen to the user' doesn't match the selected view.
Expected: is displayed on the screen to the user
Got: "TextView{id=-1, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Successfully added video, input-type=0, ime-target=false, has-links=false}"
Launcher Activity in 2.2.0 shortly shows a dissappearing card. Fix/improvement is likely to adjust default visibility for that UI item.
When a video is not available anymore (at your location?), the current message "Check your Internet connection" is misleading.
The Google/YouTube account chooser should be improved:
java.lang.IllegalStateException: Activity has been destroyed
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1433)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:687)
at android.app.BackStackRecord.commit(BackStackRecord.java:663)
at com.lambdasoup.watchlater.AddActivity$FragmentCoordinator.showFragment(AddActivity.java:684)
at com.lambdasoup.watchlater.AddActivity$FragmentCoordinator.showProgress(AddActivity.java:660)
at com.lambdasoup.watchlater.AddActivity.setPlaylistIdAndRetry(AddActivity.java:430)
at com.lambdasoup.watchlater.AddActivity.addToWatchLaterAndShow(AddActivity.java:189)
at com.lambdasoup.watchlater.AddActivity.lambda$setAuthTokenAndRetry$5(AddActivity.java:269)
at com.lambdasoup.watchlater.AddActivity.access$lambda$2(AddActivity.java)
at com.lambdasoup.watchlater.AddActivity$$Lambda$5.run(Unknown Source)
at android.accounts.AccountManager$18.run(AccountManager.java:1846)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1280)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1291)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:548)
at android.app.BackStackRecord.commit(BackStackRecord.java:532)
at com.lambdasoup.watchlater.AddActivity$FragmentCoordinator.showFragment(AddActivity.java:684)
at com.lambdasoup.watchlater.AddActivity$FragmentCoordinator.showError(AddActivity.java:668)
at com.lambdasoup.watchlater.AddActivity.showError(AddActivity.java:388)
at com.lambdasoup.watchlater.AddActivity.access$lambda$1(AddActivity.java)
at com.lambdasoup.watchlater.AddActivity$$Lambda$4.apply(Unknown Source)
at com.lambdasoup.watchlater.AddActivity$WatchlaterResult.apply(AddActivity.java:561)
at com.lambdasoup.watchlater.AddActivity.addToWatchLaterAndShow(AddActivity.java:173)
at com.lambdasoup.watchlater.AddActivity.onResult(AddActivity.java:352)
at com.lambdasoup.watchlater.AddActivity.lambda$setAuthTokenAndRetry$5(AddActivity.java:271)
at com.lambdasoup.watchlater.AddActivity.access$lambda$2(AddActivity.java)
at com.lambdasoup.watchlater.AddActivity$$Lambda$5.run(Unknown Source)
at android.accounts.AccountManager$11.run(AccountManager.java:1327)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4827)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
at dalvik.system.NativeStart.main(Native Method)
A friendly Google Play mail noted that we have to provide a Privacy Policy document in the store listing aswell as within the app. The store listing now points to the following URL:
https://lambdasoup.com/privacypolicy-watchlater/
The app should have a button/link opening a browser with this URL, too.
Add German localization
Main activity should still be a dialog. On create, fetch status of video with regards to watchlater playlist of chosen channel/account:
Android 10 has a somewhat broken behaviour regarding "default apps": When YouTube is set to "Always ask" it will in fact always ask, regardless of whether Watch Later is set to "Always open".
The instructions in the Launcher Activity should at least display a message for Android 10 users noting that.
Also look into whether this can be detected by Watch Later.
Observed:
12-17 15:24:01.123 10900-11271/com.lambdasoup.watchlater D/Retrofit: ---> HTTP GET https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&maxResults=50&mine=true
12-17 15:24:01.123 10900-11271/com.lambdasoup.watchlater D/Retrofit: Authorization: Bearer XXXXXXXXXXXXXXXXX
12-17 15:24:01.123 10900-11271/com.lambdasoup.watchlater D/Retrofit: ---> END HTTP (no body)
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: <--- HTTP 200 https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&maxResults=50&mine=true (132ms)
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: OkHttp-Selected-Protocol: h2
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: expires: Thu, 17 Dec 2015 14:24:08 GMT
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: date: Thu, 17 Dec 2015 14:24:08 GMT
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: cache-control: private, max-age=300, must-revalidate, no-transform
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: etag: "kuL0kDMAqRo3pU7O0pwlO-Lfzp4/TjcejXDE4MTg8Poo7zs699673FI"
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: vary: Origin
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: vary: X-Origin
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: content-type: application/json; charset=UTF-8
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: x-content-type-options: nosniff
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: x-frame-options: SAMEORIGIN
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: x-xss-protection: 1; mode=block
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: content-length: 191
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: server: GSE
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: alternate-protocol: 443:quic,p=1
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: alt-svc: quic=":443"; ma=604800; v="30,29,28,27,26,25"
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: OkHttp-Sent-Millis: 1450362241124
12-17 15:24:01.256 10900-11271/com.lambdasoup.watchlater D/Retrofit: OkHttp-Received-Millis: 1450362241256
12-17 15:24:01.257 10900-11271/com.lambdasoup.watchlater D/Retrofit: {
"kind": "youtube#channelListResponse",
"etag": "\"kuL0kDMAqRo3pU7O0pwlO-Lfzp4/TjcejXDE4MTg8Poo7zs699673FI\"",
"pageInfo": {
"totalResults": 0,
"resultsPerPage": 0
},
"items": []
}
12-17 15:24:01.257 10900-11271/com.lambdasoup.watchlater D/Retrofit: <--- END HTTP (191-byte body)
12-17 15:24:01.264 10900-10900/com.lambdasoup.watchlater D/AndroidRuntime: Shutting down VM
12-17 15:24:01.264 10900-10900/com.lambdasoup.watchlater E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.lambdasoup.watchlater, PID: 10900
java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at com.lambdasoup.watchlater.AddActivity$3.success(AddActivity.java:473)
at com.lambdasoup.watchlater.AddActivity$3.success(AddActivity.java:470)
at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
As documented in [1], the Watch Later playlist ID will be just "WL" in one month. We already heard about this magic ID, but now at least we have some reference.
So it seems there is good and bad in this:
Bad:
We only have one month to update the app. Slower updating users will have a broken app.
Good:
We will save one (or even two?) requests from now on.
[1] https://developers.google.com/youtube/v3/revision_history
Title, maybe length, maybe thumbnail - whatever we got in our response anyway, without making another request.
UI tests are still intermittently failing. Problems seen so far:
Time for a service!
Wished for improvements over youtube app:
Have account, playlist and permissions set up. Go in airplane mode. Launch (example) add screen.
Observe crash due to:
java.lang.RuntimeException: Tea task executor threw exception
at com.lambdasoup.tea.Tea$runBg$1$2.invoke(Tea.kt:81)
at com.lambdasoup.tea.Tea$runBg$1$2.invoke(Tea.kt:80)
at com.lambdasoup.tea.Tea$DefaultEngine.post$lambda-2$lambda-1(Tea.kt:137)
at com.lambdasoup.tea.Tea$DefaultEngine.$r8$lambda$D5grVRbrpz6uzL7Zldln94I-EHs(Unknown Source:0)
at com.lambdasoup.tea.Tea$DefaultEngine$$ExternalSyntheticLambda1.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: java.lang.RuntimeException: could not get token
at com.lambdasoup.watchlater.data.AccountRepository.getAuthToken(AccountRepository.kt:94)
at com.lambdasoup.watchlater.viewmodel.AddViewModel$getAuthToken$1.invoke(AddViewModel.kt:54)
at com.lambdasoup.watchlater.viewmodel.AddViewModel$getAuthToken$1.invoke(AddViewModel.kt:53)
at com.lambdasoup.tea.Cmd$Companion$task$1$1.invoke(Cmd.kt:51)
at com.lambdasoup.tea.Tea$runBg$1.invoke(Tea.kt:75)
at com.lambdasoup.tea.Tea$runBg$1.invoke(Tea.kt:73)
at com.lambdasoup.tea.Tea$DefaultEngine.execute$lambda-0(Tea.kt:136)
at com.lambdasoup.tea.Tea$DefaultEngine.$r8$lambda$10Qi51QdNYyh2l4FRDX_Qwxlrbw(Unknown Source:0)
at com.lambdasoup.tea.Tea$DefaultEngine$$ExternalSyntheticLambda0.run(Unknown Source:2)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
Caused by: java.io.IOException: NetworkError
at android.accounts.AccountManager.convertErrorToException(AccountManager.java:2628)
at android.accounts.AccountManager.-$$Nest$mconvertErrorToException(Unknown Source:0)
at android.accounts.AccountManager$AmsTask$Response.onError(AccountManager.java:2479)
at android.accounts.IAccountManagerResponse$Stub.onTransact(IAccountManagerResponse.java:107)
at android.os.Binder.execTransactInternal(Binder.java:1285)
at android.os.Binder.execTransact(Binder.java:1244)
Currently, on 403 the message "Your Watch Later playlist is either full or already contains this video" is shown. This is likely misleading, as the problem is an authorization problem in that case.
If the playlist actually contains the video already,
"error": {
"errors": [
{
"domain": "youtube.playlistItem",
"reason": "videoAlreadyInPlaylist",
"message": "Video already in playlist."
}
],
"code": 409,
"message": "Video already in playlist."
}
}
is returned. An appropriate message should be shown.
The app should also let you choose which account to use in case you have multiple YouTube accounts/channels.
java.lang.IllegalStateException: Cannot add header view to list -- setAdapter has already been called.
at android.widget.ListView.addHeaderView(ListView.java:258)
at android.widget.ListView.addHeaderView(ListView.java:287)
at com.lambdasoup.watchlater.AddActivity.onMultipleAccounts(AddActivity.java:224)
Android 6.0 Marshmallow has been released. We need to check AppLinks and runtime permissions.
when the video is not found, the YouTube API does not (anymore?) answer with an HTTP error status but an empty list response. This crashes the app.
The URI schema forces us at least in some occasions (e.g. http://www.youtube.com/attribution_link?u=/playlist%3Flist%3DPL0INsTTU1k2UO-2-AwomFmAs4nuZU9ht3%26feature%3Dem-share_playlist_user ) to accept intents for adding playlists.
Those could be handled by adding all videos in the playlist to watchlater, or by saving the playlist for the user (arguably outside the watchlater scope and can easily be done by just using the Youtube app for the intent and pressing the + icon there).
1 year working but suddenly appears this message with every video, including cat example.
Thanks and regards!
If account and playlist are already selected, and then a new account is added and switched to, the state is definitely not the desired one: the old playlist still gets shown, and trying to select a different one seems to result in a bad request to the YouTube API.
One second I was adding videos. The next second none of the videos will add anymore. I cleared the data from the app and then tried again, added my YouTube account and still it won't work.
We got asked to support other YouTube clients like NewPipr, Vanced, SmartTube, too. At the moment, the "Watch Now" button hardcodedly opens the official YouTube app.
Just as an idea: Maybe take all apps with youtube-URL intent filters and remove "Watch Later" from it.
Check compatibility with Android M, especially:
Especially people coming from v1 might miss the automatic add behaviour after getting the v2 update. We could make a "add automatically" option for users who want to keep the old usage pattern.
When the device does not have a Google account added, the app will only display an empty screen. The app should display a message explaining that such an account is needed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.