diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0ac41937..d718f0080 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,19 +43,19 @@ - + + android:windowSoftInputMode="adjustResize" /> - - + + diff --git a/app/src/main/graphql/com/github/andreyasadchy/xtra/schema.graphqls b/app/src/main/graphql/com/github/andreyasadchy/xtra/schema.graphqls index ad3db3db7..3884020b5 100644 --- a/app/src/main/graphql/com/github/andreyasadchy/xtra/schema.graphqls +++ b/app/src/main/graphql/com/github/andreyasadchy/xtra/schema.graphqls @@ -1,493 +1,493 @@ type Query { - badges: [Badge] - cheerConfig: GlobalCheerConfig - games(first: Int, after: Cursor, options: GameOptions): GameConnection - game(id: ID, slug: String, name: String): Game - searchCategories(query: String!, first: Int, after: Cursor): SearchCategoriesConnection - searchFor(userQuery: String!, platform: String!, target: SearchForTarget): SearchFor - searchStreams(userQuery: String!, first: Int, after: Cursor): SearchStreamConnection - searchUsers(userQuery: String!, first: Int, after: Cursor): SearchUserConnection - streams(first: Int, after: Cursor, options: StreamOptions): StreamConnection - user(id: ID, login: String, lookupType: UserLookupType): User - userResultByID(id: ID!): UserResult - userResultByLogin(login: String!): UserResult - users(ids: [ID!], logins: [String!]): [User] - video(id: ID): Video + badges: [Badge] + cheerConfig: GlobalCheerConfig + games(first: Int, after: Cursor, options: GameOptions): GameConnection + game(id: ID, slug: String, name: String): Game + searchCategories(query: String!, first: Int, after: Cursor): SearchCategoriesConnection + searchFor(userQuery: String!, platform: String!, target: SearchForTarget): SearchFor + searchStreams(userQuery: String!, first: Int, after: Cursor): SearchStreamConnection + searchUsers(userQuery: String!, first: Int, after: Cursor): SearchUserConnection + streams(first: Int, after: Cursor, options: StreamOptions): StreamConnection + user(id: ID, login: String, lookupType: UserLookupType): User + userResultByID(id: ID!): UserResult + userResultByLogin(login: String!): UserResult + users(ids: [ID!], logins: [String!]): [User] + video(id: ID): Video } type Badge { - imageURL(size: BadgeImageSize): String - setID: ID - title: String - version: String + imageURL(size: BadgeImageSize): String + setID: ID + title: String + version: String } type Broadcast { - startedAt: Time + startedAt: Time } type BroadcastSettings { - title: String + title: String } type ChannelNotificationSettings { - isEnabled: Boolean + isEnabled: Boolean } type CheerInfo { - cheerGroups: [CheermoteGroup!] + cheerGroups: [CheermoteGroup!] } type Cheermote { - prefix: String - tiers: [CheermoteTier] + prefix: String + tiers: [CheermoteTier] } type CheermoteColorConfig { - bits: Int - color: String + bits: Int + color: String } type CheermoteDisplayConfig { - backgrounds: [String!] - colors: [CheermoteColorConfig!] - scales: [String!] - types: [CheermoteDisplayType!] + backgrounds: [String!] + colors: [CheermoteColorConfig!] + scales: [String!] + types: [CheermoteDisplayType!] } type CheermoteDisplayType { - animation: String - extension: String + animation: String + extension: String } type CheermoteGroup { - nodes: [Cheermote!] - templateURL: String + nodes: [Cheermote!] + templateURL: String } type CheermoteTier { - bits: Int + bits: Int } type Clip { - broadcaster: User - createdAt: Time - durationSeconds: Int - game: Game - id: ID - slug: String - thumbnailURL: String - title: String - video: Video - videoOffsetSeconds: Int - viewCount: Int + broadcaster: User + createdAt: Time + durationSeconds: Int + game: Game + id: ID + slug: String + thumbnailURL: String + title: String + video: Video + videoOffsetSeconds: Int + viewCount: Int } type ClipConnection { - edges: [ClipEdge] - pageInfo: PageInfo + edges: [ClipEdge] + pageInfo: PageInfo } type ClipEdge { - cursor: Cursor - node: Clip + cursor: Cursor + node: Clip } type Emote { - id: ID - owner: User - setID: ID - token: String - type: EmoteType + id: ID + owner: User + setID: ID + token: String + type: EmoteType } type EmoteSet { - emotes: [Emote] + emotes: [Emote] } type Follow { - followedAt: Time + followedAt: Time } type FollowConnection { - edges: [FollowEdge] - pageInfo: PageInfo - totalCount: Int + edges: [FollowEdge] + pageInfo: PageInfo + totalCount: Int } type FollowedGameConnection { - nodes: [Game] + nodes: [Game] } type FollowEdge { - cursor: Cursor - followedAt: Time - node: User + cursor: Cursor + followedAt: Time + node: User } type FollowedLiveUserConnection { - edges: [FollowedLiveUserEdge] - pageInfo: PageInfo + edges: [FollowedLiveUserEdge] + pageInfo: PageInfo } type FollowedLiveUserEdge { - cursor: Cursor - node: User + cursor: Cursor + node: User } type FollowerConnection { - totalCount: Int + totalCount: Int } type FollowerEdge { - notificationSettings: ChannelNotificationSettings + notificationSettings: ChannelNotificationSettings } type FreeformTag { - name: String + name: String } type Game { - boxArtURL(width: Int, height: Int): String - broadcastersCount: Int - clips(first: Int, after: Cursor, criteria: GameClipsInput): ClipConnection - displayName: String - id: ID - slug: String - streams(first: Int, after: Cursor, options: GameStreamOptions): StreamConnection - tags(tagType: TagType!): [Tag!] - videos(first: Int, after: Cursor, languages: [String!], types: [BroadcastType!], sort: VideoSort): VideoConnection - viewersCount: Int + boxArtURL(width: Int, height: Int): String + broadcastersCount: Int + clips(first: Int, after: Cursor, criteria: GameClipsInput): ClipConnection + displayName: String + id: ID + slug: String + streams(first: Int, after: Cursor, options: GameStreamOptions): StreamConnection + tags(tagType: TagType!): [Tag!] + videos(first: Int, after: Cursor, languages: [String!], types: [BroadcastType!], sort: VideoSort): VideoConnection + viewersCount: Int } type GameConnection { - edges: [GameEdge] - pageInfo: PageInfo + edges: [GameEdge] + pageInfo: PageInfo } type GameEdge { - cursor: Cursor - node: Game + cursor: Cursor + node: Game } type GlobalCheerConfig { - displayConfig: CheermoteDisplayConfig - groups: [CheermoteGroup!] + displayConfig: CheermoteDisplayConfig + groups: [CheermoteGroup!] } type PageInfo { - hasNextPage: Boolean - hasPreviousPage: Boolean + hasNextPage: Boolean + hasPreviousPage: Boolean } type SearchCategoriesConnection { - edges: [SearchCategoriesEdge!] - pageInfo: PageInfo + edges: [SearchCategoriesEdge!] + pageInfo: PageInfo } type SearchCategoriesEdge { - cursor: Cursor - node: Game + cursor: Cursor + node: Game } type SearchFor { - channels: SearchForResultUsers - games: SearchForResultGames - videos: SearchForResultVideos + channels: SearchForResultUsers + games: SearchForResultGames + videos: SearchForResultVideos } type SearchForResultGames { - cursor: String - items: [Game!] - pageInfo: PageInfo + cursor: String + items: [Game!] + pageInfo: PageInfo } type SearchForResultUsers { - cursor: String - items: [User!] - pageInfo: PageInfo + cursor: String + items: [User!] + pageInfo: PageInfo } type SearchForResultVideos { - cursor: String - items: [Video!] - pageInfo: PageInfo + cursor: String + items: [Video!] + pageInfo: PageInfo } type SearchStreamConnection { - edges: [SearchStreamEdge!] - pageInfo: PageInfo + edges: [SearchStreamEdge!] + pageInfo: PageInfo } type SearchStreamEdge { - cursor: Cursor - node: Stream + cursor: Cursor + node: Stream } type SearchUserConnection { - edges: [SearchUserEdge!] - pageInfo: PageInfo + edges: [SearchUserEdge!] + pageInfo: PageInfo } type SearchUserEdge { - cursor: Cursor - node: User + cursor: Cursor + node: User } type Stream { - broadcaster: User - createdAt: Time - freeformTags: [FreeformTag!] - game: Game - id: ID - previewImageURL: String - title: String - type: String - viewersCount: Int + broadcaster: User + createdAt: Time + freeformTags: [FreeformTag!] + game: Game + id: ID + previewImageURL: String + title: String + type: String + viewersCount: Int } type StreamConnection { - edges: [StreamEdge] - pageInfo: PageInfo + edges: [StreamEdge] + pageInfo: PageInfo } type StreamEdge { - cursor: Cursor - node: Stream + cursor: Cursor + node: Stream } type Tag { - id: ID - localizedName: String - scope: TagScope + id: ID + localizedName: String + scope: TagScope } type User { - bannerImageURL: String - broadcastBadges: [Badge] - broadcastSettings: BroadcastSettings, - cheer: CheerInfo - clips(first: Int, after: Cursor, criteria: UserClipsInput): ClipConnection - createdAt: Time - description: String - displayName: String - emoteSets: [EmoteSet!] - follow(targetID: ID, targetLogin: String): Follow - followedGames(first: Int, type: FollowedGamesType): FollowedGameConnection - followedLiveUsers(first: Int, after: Cursor, sort: StreamSort): FollowedLiveUserConnection - followedVideos(first: Int, after: Cursor, languages: [String!], types: [BroadcastType!], sort: VideoSort): VideoConnection - followers: FollowerConnection - follows(first: Int, after: Cursor, order: SortOrder): FollowConnection - id: ID - login: String - lastBroadcast: Broadcast - profileImageURL(width: Int): String - roles: UserRoles - self: UserSelfConnection - stream: Stream - videos(first: Int, after: Cursor, types: [BroadcastType!], sort: VideoSort): VideoConnection + bannerImageURL: String + broadcastBadges: [Badge] + broadcastSettings: BroadcastSettings, + cheer: CheerInfo + clips(first: Int, after: Cursor, criteria: UserClipsInput): ClipConnection + createdAt: Time + description: String + displayName: String + emoteSets: [EmoteSet!] + follow(targetID: ID, targetLogin: String): Follow + followedGames(first: Int, type: FollowedGamesType): FollowedGameConnection + followedLiveUsers(first: Int, after: Cursor, sort: StreamSort): FollowedLiveUserConnection + followedVideos(first: Int, after: Cursor, languages: [String!], types: [BroadcastType!], sort: VideoSort): VideoConnection + followers: FollowerConnection + follows(first: Int, after: Cursor, order: SortOrder): FollowConnection + id: ID + login: String + lastBroadcast: Broadcast + profileImageURL(width: Int): String + roles: UserRoles + self: UserSelfConnection + stream: Stream + videos(first: Int, after: Cursor, types: [BroadcastType!], sort: VideoSort): VideoConnection } type UserDoesNotExist { - key: String! - reason: String! + key: String! + reason: String! } type UserError { - key: String! + key: String! } type UserRoles { - isAffiliate: Boolean - isPartner: Boolean - isStaff: Boolean + isAffiliate: Boolean + isPartner: Boolean + isStaff: Boolean } type UserSelfConnection { - follower: FollowerEdge + follower: FollowerEdge } type Video { - animatedPreviewURL: String - broadcastType: BroadcastType - contentTags: [Tag!] - createdAt: Time - game: Game - id: ID - lengthSeconds: Int - owner: User - previewThumbnailURL: String - title: String - viewCount: Int + animatedPreviewURL: String + broadcastType: BroadcastType + contentTags: [Tag!] + createdAt: Time + game: Game + id: ID + lengthSeconds: Int + owner: User + previewThumbnailURL: String + title: String + viewCount: Int } type VideoConnection { - edges: [VideoEdge] - pageInfo: PageInfo + edges: [VideoEdge] + pageInfo: PageInfo } type VideoEdge { - cursor: Cursor - node: Video + cursor: Cursor + node: Video } input GameClipsInput { - languages: [Language!] - period: ClipsPeriod - sort: ClipsSort + languages: [Language!] + period: ClipsPeriod + sort: ClipsSort } input GameOptions { - tags: [String!] + tags: [String!] } input GameStreamOptions { - freeformTags: [String!] - sort: StreamSort + freeformTags: [String!] + sort: StreamSort } input SearchForTarget { - cursor: String - index: SearchIndex - limit: Int + cursor: String + index: SearchIndex + limit: Int } input StreamOptions { - freeformTags: [String!] - sort: StreamSort + freeformTags: [String!] + sort: StreamSort } input UserClipsInput { - period: ClipsPeriod - sort: ClipsSort + period: ClipsPeriod + sort: ClipsSort } enum BadgeImageSize { - NORMAL - DOUBLE - QUADRUPLE + NORMAL + DOUBLE + QUADRUPLE } enum BroadcastType { - ARCHIVE - HIGHLIGHT - UPLOAD - PREMIERE_UPLOAD - PAST_PREMIERE + ARCHIVE + HIGHLIGHT + UPLOAD + PREMIERE_UPLOAD + PAST_PREMIERE } enum ClipsPeriod { - LAST_DAY - LAST_WEEK - LAST_MONTH - ALL_TIME + LAST_DAY + LAST_WEEK + LAST_MONTH + ALL_TIME } enum ClipsSort { - CREATED_AT_ASC - CREATED_AT_DESC - VIEWS_ASC - VIEWS_DESC - TRENDING + CREATED_AT_ASC + CREATED_AT_DESC + VIEWS_ASC + VIEWS_DESC + TRENDING } enum EmoteType { - CHANNEL_POINTS - BITS_BADGE_TIERS - SUBSCRIPTIONS - PRIME - TURBO - TWO_FACTOR - SMILIES - GLOBALS - LIMITED_TIME - HYPE_TRAIN - MEGA_COMMERCE - ARCHIVE - FOLLOWER - UNKNOWN + CHANNEL_POINTS + BITS_BADGE_TIERS + SUBSCRIPTIONS + PRIME + TURBO + TWO_FACTOR + SMILIES + GLOBALS + LIMITED_TIME + HYPE_TRAIN + MEGA_COMMERCE + ARCHIVE + FOLLOWER + UNKNOWN } enum FollowedGamesType { - LIVE - ALL + LIVE + ALL } enum Language { - AR - ASL - BG - CA - CS - DA - DE - EL - EN - ES - FI - FR - HI - HU - ID - IT - JA - KO - MS - NL - NO - OTHER - PL - PT - RO - RU - SK - SV - TH - TL - TR - UK - VI - ZH - ZH_HK + AR + ASL + BG + CA + CS + DA + DE + EL + EN + ES + FI + FR + HI + HU + ID + IT + JA + KO + MS + NL + NO + OTHER + PL + PT + RO + RU + SK + SV + TH + TL + TR + UK + VI + ZH + ZH_HK } enum SearchIndex { - GAME - VOD - CHANNEL + GAME + VOD + CHANNEL } enum SortOrder { - ASC - DESC + ASC + DESC } enum StreamSort { - VIEWER_COUNT - VIEWER_COUNT_ASC - RECENT - RELEVANCE + VIEWER_COUNT + VIEWER_COUNT_ASC + RECENT + RELEVANCE } enum TagScope { - ALL - CATEGORY + ALL + CATEGORY } enum TagType { - CONTENT - TOP + CONTENT + TOP } enum UserLookupType { - ACTIVE - ALL + ACTIVE + ALL } enum VideoSort { - TIME - TIME_ASC - VIEWS + TIME + TIME_ASC + VIEWS } union UserResult = User | UserDoesNotExist | UserError @@ -496,5 +496,5 @@ scalar Cursor scalar Time schema { - query: Query + query: Query } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/api/GraphQLApi.kt b/app/src/main/java/com/github/andreyasadchy/xtra/api/GraphQLApi.kt index 671ea5993..74ebc5446 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/api/GraphQLApi.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/api/GraphQLApi.kt @@ -47,164 +47,326 @@ import retrofit2.http.POST interface GraphQLApi { @POST(".") - suspend fun getPlaybackAccessToken(@HeaderMap headers: Map, @Body json: JsonObject): PlaybackAccessTokenResponse + suspend fun getPlaybackAccessToken( + @HeaderMap headers: Map, + @Body json: JsonObject + ): PlaybackAccessTokenResponse @POST(".") - suspend fun getClipUrls(@HeaderMap headers: Map, @Body json: JsonObject): ClipUrlsResponse + suspend fun getClipUrls( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ClipUrlsResponse @POST(".") - suspend fun getClipData(@HeaderMap headers: Map, @Body json: JsonObject): ClipDataResponse + suspend fun getClipData( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ClipDataResponse @POST(".") - suspend fun getClipVideo(@HeaderMap headers: Map, @Body json: JsonObject): ClipVideoResponse + suspend fun getClipVideo( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ClipVideoResponse @POST(".") - suspend fun getTopGames(@HeaderMap headers: Map, @Body json: JsonObject): GamesResponse + suspend fun getTopGames( + @HeaderMap headers: Map, + @Body json: JsonObject + ): GamesResponse @POST(".") - suspend fun getTopStreams(@HeaderMap headers: Map, @Body json: JsonObject): StreamsResponse + suspend fun getTopStreams( + @HeaderMap headers: Map, + @Body json: JsonObject + ): StreamsResponse @POST(".") - suspend fun getGameStreams(@HeaderMap headers: Map, @Body json: JsonObject): GameStreamsResponse + suspend fun getGameStreams( + @HeaderMap headers: Map, + @Body json: JsonObject + ): GameStreamsResponse @POST(".") - suspend fun getGameVideos(@HeaderMap headers: Map, @Body json: JsonObject): GameVideosResponse + suspend fun getGameVideos( + @HeaderMap headers: Map, + @Body json: JsonObject + ): GameVideosResponse @POST(".") - suspend fun getGameClips(@HeaderMap headers: Map, @Body json: JsonObject): GameClipsResponse + suspend fun getGameClips( + @HeaderMap headers: Map, + @Body json: JsonObject + ): GameClipsResponse @POST(".") - suspend fun getChannelVideos(@HeaderMap headers: Map, @Body json: JsonObject): ChannelVideosResponse + suspend fun getChannelVideos( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ChannelVideosResponse @POST(".") - suspend fun getChannelClips(@HeaderMap headers: Map, @Body json: JsonObject): ChannelClipsResponse + suspend fun getChannelClips( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ChannelClipsResponse @POST(".") - suspend fun getSearchChannels(@HeaderMap headers: Map, @Body json: JsonObject): SearchChannelsResponse + suspend fun getSearchChannels( + @HeaderMap headers: Map, + @Body json: JsonObject + ): SearchChannelsResponse @POST(".") - suspend fun getSearchGames(@HeaderMap headers: Map, @Body json: JsonObject): SearchGamesResponse + suspend fun getSearchGames( + @HeaderMap headers: Map, + @Body json: JsonObject + ): SearchGamesResponse @POST(".") - suspend fun getSearchVideos(@HeaderMap headers: Map, @Body json: JsonObject): SearchVideosResponse + suspend fun getSearchVideos( + @HeaderMap headers: Map, + @Body json: JsonObject + ): SearchVideosResponse @POST(".") - suspend fun getFreeformTags(@HeaderMap headers: Map, @Body json: JsonObject): SearchStreamTagsResponse + suspend fun getFreeformTags( + @HeaderMap headers: Map, + @Body json: JsonObject + ): SearchStreamTagsResponse @POST(".") - suspend fun getGameTags(@HeaderMap headers: Map, @Body json: JsonObject): SearchGameTagsResponse + suspend fun getGameTags( + @HeaderMap headers: Map, + @Body json: JsonObject + ): SearchGameTagsResponse @POST(".") - suspend fun getChatBadges(@HeaderMap headers: Map, @Body json: JsonObject): BadgesResponse + suspend fun getChatBadges( + @HeaderMap headers: Map, + @Body json: JsonObject + ): BadgesResponse @POST(".") - suspend fun getGlobalCheerEmotes(@HeaderMap headers: Map, @Body json: JsonObject): GlobalCheerEmotesResponse + suspend fun getGlobalCheerEmotes( + @HeaderMap headers: Map, + @Body json: JsonObject + ): GlobalCheerEmotesResponse @POST(".") - suspend fun getChannelCheerEmotes(@HeaderMap headers: Map, @Body json: JsonObject): ChannelCheerEmotesResponse + suspend fun getChannelCheerEmotes( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ChannelCheerEmotesResponse @POST(".") - suspend fun getVideoMessages(@HeaderMap headers: Map, @Body json: JsonObject): VideoMessagesResponse + suspend fun getVideoMessages( + @HeaderMap headers: Map, + @Body json: JsonObject + ): VideoMessagesResponse @POST(".") - suspend fun getVideoMessagesDownload(@HeaderMap headers: Map, @Body json: JsonObject): JsonElement + suspend fun getVideoMessagesDownload( + @HeaderMap headers: Map, + @Body json: JsonObject + ): JsonElement @POST(".") - suspend fun getVideoGames(@HeaderMap headers: Map, @Body json: JsonObject): VideoGamesResponse + suspend fun getVideoGames( + @HeaderMap headers: Map, + @Body json: JsonObject + ): VideoGamesResponse @POST(".") - suspend fun getChannelViewerList(@HeaderMap headers: Map, @Body json: JsonObject): ChannelViewerListResponse + suspend fun getChannelViewerList( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ChannelViewerListResponse @POST(".") - suspend fun getViewerCount(@HeaderMap headers: Map, @Body json: JsonObject): ViewerCountResponse + suspend fun getViewerCount( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ViewerCountResponse @POST(".") - suspend fun getEmoteCard(@HeaderMap headers: Map, @Body json: JsonObject): EmoteCardResponse + suspend fun getEmoteCard( + @HeaderMap headers: Map, + @Body json: JsonObject + ): EmoteCardResponse @POST(".") - suspend fun getChannelPanel(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun getChannelPanel( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun getFollowedStreams(@HeaderMap headers: Map, @Body json: JsonObject): FollowedStreamsResponse + suspend fun getFollowedStreams( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowedStreamsResponse @POST(".") - suspend fun getFollowedVideos(@HeaderMap headers: Map, @Body json: JsonObject): FollowedVideosResponse + suspend fun getFollowedVideos( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowedVideosResponse @POST(".") - suspend fun getFollowedChannels(@HeaderMap headers: Map, @Body json: JsonObject): FollowedChannelsResponse + suspend fun getFollowedChannels( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowedChannelsResponse @POST(".") - suspend fun getFollowedGames(@HeaderMap headers: Map, @Body json: JsonObject): FollowedGamesResponse + suspend fun getFollowedGames( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowedGamesResponse @POST(".") - suspend fun getFollowUser(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getFollowUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getUnfollowUser(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getUnfollowUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getToggleNotificationsUser(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getToggleNotificationsUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getFollowGame(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getFollowGame( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getUnfollowGame(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getUnfollowGame( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getFollowingUser(@HeaderMap headers: Map, @Body json: JsonObject): FollowingUserResponse + suspend fun getFollowingUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowingUserResponse @POST(".") - suspend fun getFollowingGame(@HeaderMap headers: Map, @Body json: JsonObject): FollowingGameResponse + suspend fun getFollowingGame( + @HeaderMap headers: Map, + @Body json: JsonObject + ): FollowingGameResponse @POST(".") - suspend fun getChannelPointsContext(@HeaderMap headers: Map, @Body json: JsonObject): ChannelPointContextResponse + suspend fun getChannelPointsContext( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ChannelPointContextResponse @POST(".") - suspend fun getClaimPoints(@HeaderMap headers: Map, @Body json: JsonObject): ErrorResponse + suspend fun getClaimPoints( + @HeaderMap headers: Map, + @Body json: JsonObject + ): ErrorResponse @POST(".") - suspend fun getJoinRaid(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun getJoinRaid( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun getUserEmotes(@HeaderMap headers: Map, @Body json: JsonObject): UserEmotesResponse + suspend fun getUserEmotes( + @HeaderMap headers: Map, + @Body json: JsonObject + ): UserEmotesResponse @POST(".") - suspend fun sendAnnouncement(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun sendAnnouncement( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun banUser(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun banUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun unbanUser(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun unbanUser( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun updateChatColor(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun updateChatColor( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun createStreamMarker(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun createStreamMarker( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun getModerators(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun getModerators( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun addModerator(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun addModerator( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun removeModerator(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun removeModerator( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun startRaid(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun startRaid( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun cancelRaid(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun cancelRaid( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun getVips(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun getVips( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun addVip(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun addVip( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response @POST(".") - suspend fun removeVip(@HeaderMap headers: Map, @Body json: JsonObject): Response + suspend fun removeVip( + @HeaderMap headers: Map, + @Body json: JsonObject + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/api/UsherApi.kt b/app/src/main/java/com/github/andreyasadchy/xtra/api/UsherApi.kt index e573e57a0..427a7889d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/api/UsherApi.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/api/UsherApi.kt @@ -9,8 +9,14 @@ import retrofit2.http.QueryMap interface UsherApi { @GET("api/channel/hls/{channel}.m3u8") - suspend fun getStreamPlaylist(@Path("channel") channel: String?, @QueryMap options: Map): Response + suspend fun getStreamPlaylist( + @Path("channel") channel: String?, + @QueryMap options: Map + ): Response @GET("vod/{id}.m3u8") - suspend fun getVideoPlaylist(@Path("id") id: String?, @QueryMap options: Map): Response + suspend fun getVideoPlaylist( + @Path("id") id: String?, + @QueryMap options: Map + ): Response } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/db/AppDatabase.kt b/app/src/main/java/com/github/andreyasadchy/xtra/db/AppDatabase.kt index 544ef65ca..e3ad6d082 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/db/AppDatabase.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/db/AppDatabase.kt @@ -14,7 +14,10 @@ import com.github.andreyasadchy.xtra.model.ui.SortChannel import com.github.andreyasadchy.xtra.model.ui.SortGame import com.github.andreyasadchy.xtra.model.ui.VodBookmarkIgnoredUser -@Database(entities = [OfflineVideo::class, RecentEmote::class, VideoPosition::class, LocalFollowChannel::class, LocalFollowGame::class, Bookmark::class, VodBookmarkIgnoredUser::class, SortChannel::class, SortGame::class, ShownNotification::class, NotificationUser::class], version = 28) +@Database( + entities = [OfflineVideo::class, RecentEmote::class, VideoPosition::class, LocalFollowChannel::class, LocalFollowGame::class, Bookmark::class, VodBookmarkIgnoredUser::class, SortChannel::class, SortGame::class, ShownNotification::class, NotificationUser::class], + version = 28 +) abstract class AppDatabase : RoomDatabase() { abstract fun videos(): VideosDao diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/di/AppInjector.kt b/app/src/main/java/com/github/andreyasadchy/xtra/di/AppInjector.kt index ab8053f4a..c60c1459a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/di/AppInjector.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/di/AppInjector.kt @@ -15,19 +15,21 @@ object AppInjector { xtraApp.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { if (activity is FragmentActivity) { - activity.supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) { - if (f is LifecycleListener) { - xtraApp.addLifecycleListener(f) + activity.supportFragmentManager.registerFragmentLifecycleCallbacks( + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) { + if (f is LifecycleListener) { + xtraApp.addLifecycleListener(f) + } } - } - override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) { - if (f is LifecycleListener) { - xtraApp.removeLifecycleListener(f) + override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) { + if (f is LifecycleListener) { + xtraApp.removeLifecycleListener(f) + } } - } - }, true) + }, true + ) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/di/DatabaseModule.kt b/app/src/main/java/com/github/andreyasadchy/xtra/di/DatabaseModule.kt index 9f464de9e..fdc84cd25 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/di/DatabaseModule.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/di/DatabaseModule.kt @@ -118,164 +118,164 @@ class DatabaseModule { @Singleton @Provides fun providesAppDatabase(application: Application): AppDatabase = - Room.databaseBuilder(application, AppDatabase::class.java, "database") - .addMigrations( - object : Migration(9, 10) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("DELETE FROM emotes") - } - }, - object : Migration(10, 11) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE videos ADD COLUMN videoId TEXT DEFAULT null") - } - }, - object : Migration(11, 12) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games (game_id TEXT NOT NULL, game_name TEXT, boxArt TEXT, PRIMARY KEY (game_id))") - } - }, - object : Migration(12, 13) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT NOT NULL, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER NOT NULL, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - } - }, - object : Migration(13, 14) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER, type TEXT, videoId TEXT, is_bookmark INTEGER, userType TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id = id, is_vod = is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - db.execSQL("CREATE TABLE IF NOT EXISTS vod_bookmark_ignored_users (user_id TEXT NOT NULL, PRIMARY KEY (user_id))") - } - }, - object : Migration(14, 15) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT OR IGNORE INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id = id, is_vod = is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks (id TEXT NOT NULL, userId TEXT, userLogin TEXT, userName TEXT, userLogo TEXT, gameId TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, PRIMARY KEY (id))") - } - }, - object : Migration(15, 16) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE bookmarks ADD COLUMN userType TEXT DEFAULT null") - db.execSQL("ALTER TABLE bookmarks ADD COLUMN userBroadcasterType TEXT DEFAULT null") - } - }, - object : Migration(16, 17) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS sort_channel (id TEXT NOT NULL, saveSort INTEGER, videoSort TEXT, videoType TEXT, clipPeriod TEXT, PRIMARY KEY (id))") - db.execSQL("CREATE TABLE IF NOT EXISTS sort_game (id TEXT NOT NULL, saveSort INTEGER, videoSort TEXT, videoPeriod TEXT, videoType TEXT, videoLanguageIndex INTEGER, clipPeriod TEXT, clipLanguageIndex INTEGER, PRIMARY KEY (id))") - } - }, - object : Migration(17, 18) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS recent_emotes1 (name TEXT NOT NULL, url1x TEXT, url2x TEXT, url3x TEXT, url4x TEXT, used_at INTEGER NOT NULL, PRIMARY KEY (name))") - db.execSQL("INSERT INTO recent_emotes1 (name, url1x, used_at) SELECT name, url, used_at FROM recent_emotes") - db.execSQL("DROP TABLE recent_emotes") - db.execSQL("ALTER TABLE recent_emotes1 RENAME TO recent_emotes") - } - }, - object : Migration(18, 19) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks1 (videoId TEXT, userId TEXT, userLogin TEXT, userName TEXT, userType TEXT, userBroadcasterType TEXT, userLogo TEXT, gameId TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, animatedPreviewURL TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO bookmarks1 (videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration) SELECT id, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration FROM bookmarks") - db.execSQL("DROP TABLE bookmarks") - db.execSQL("ALTER TABLE bookmarks1 RENAME TO bookmarks") - db.execSQL("CREATE TABLE IF NOT EXISTS local_follows1 (userId TEXT, userLogin TEXT, userName TEXT, channelLogo TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO local_follows1 (userId, userLogin, userName, channelLogo) SELECT user_id, user_login, user_name, channelLogo FROM local_follows") - db.execSQL("DROP TABLE local_follows") - db.execSQL("ALTER TABLE local_follows1 RENAME TO local_follows") - db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games1 (gameId TEXT, gameName TEXT, boxArt TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO local_follows_games1 (gameId, gameName, boxArt) SELECT game_id, game_name, boxArt FROM local_follows_games") - db.execSQL("DROP TABLE local_follows_games") - db.execSQL("ALTER TABLE local_follows_games1 RENAME TO local_follows_games") - db.execSQL("CREATE TABLE IF NOT EXISTS requests1 (offline_video_id INTEGER NOT NULL, url TEXT NOT NULL, path TEXT NOT NULL, video_id TEXT, video_type TEXT, segment_from INTEGER, segment_to INTEGER, PRIMARY KEY (offline_video_id), FOREIGN KEY('offline_video_id') REFERENCES videos('id') ON DELETE CASCADE)") - db.execSQL("INSERT INTO requests1 (offline_video_id, url, path, video_id, segment_from, segment_to) SELECT offline_video_id, url, path, video_id, segment_from, segment_to FROM requests") - db.execSQL("DROP TABLE requests") - db.execSQL("ALTER TABLE requests1 RENAME TO requests") - } - }, - object : Migration(19, 20) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS recent_emotes1 (name TEXT NOT NULL, used_at INTEGER NOT NULL, PRIMARY KEY (name))") - db.execSQL("INSERT INTO recent_emotes1 (name, used_at) SELECT name, used_at FROM recent_emotes") - db.execSQL("DROP TABLE recent_emotes") - db.execSQL("ALTER TABLE recent_emotes1 RENAME TO recent_emotes") - } - }, - object : Migration(20, 21) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS requests1 (offline_video_id INTEGER NOT NULL, url TEXT NOT NULL, path TEXT NOT NULL, PRIMARY KEY (offline_video_id), FOREIGN KEY('offline_video_id') REFERENCES videos('id') ON DELETE CASCADE)") - db.execSQL("INSERT INTO requests1 (offline_video_id, url, path) SELECT offline_video_id, url, path FROM requests") - db.execSQL("DROP TABLE requests") - db.execSQL("ALTER TABLE requests1 RENAME TO requests") - } - }, - object : Migration(21, 22) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks1 (videoId TEXT, userId TEXT, userLogin TEXT, userName TEXT, userType TEXT, userBroadcasterType TEXT, userLogo TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, animatedPreviewURL TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO bookmarks1 (videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration, animatedPreviewURL, id) SELECT videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration, animatedPreviewURL, id FROM bookmarks") - db.execSQL("DROP TABLE bookmarks") - db.execSQL("ALTER TABLE bookmarks1 RENAME TO bookmarks") - db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games1 (gameId TEXT, gameSlug TEXT, gameName TEXT, boxArt TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO local_follows_games1 (gameId, gameName, boxArt, id) SELECT gameId, gameName, boxArt, id FROM local_follows_games") - db.execSQL("DROP TABLE local_follows_games") - db.execSQL("ALTER TABLE local_follows_games1 RENAME TO local_follows_games") - } - }, - object : Migration(22, 23) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, quality TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - } - }, - object : Migration(23, 24) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, quality TEXT, downloadChat INTEGER, downloadChatEmotes INTEGER, chatProgress INTEGER, chatUrl TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, id, is_vod FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - } - }, - object : Migration(24, 25) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("DROP TABLE requests") - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, bytes INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, clipId TEXT, quality TEXT, downloadChat INTEGER NOT NULL, downloadChatEmotes INTEGER NOT NULL, chatProgress INTEGER NOT NULL, maxChatProgress INTEGER NOT NULL, chatBytes INTEGER NOT NULL, chatOffsetSeconds INTEGER NOT NULL, chatUrl TEXT, playlistToFile INTEGER NOT NULL, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, bytes, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, id) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, 0, downloadPath, fromTime, toTime, status, type, videoId, quality, 0, 0, 0, 100, 0, 0, chatUrl, 0, id FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - } - }, - object : Migration(25, 26) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, bytes INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, clipId TEXT, quality TEXT, downloadChat INTEGER NOT NULL, downloadChatEmotes INTEGER NOT NULL, chatProgress INTEGER NOT NULL, maxChatProgress INTEGER NOT NULL, chatBytes INTEGER NOT NULL, chatOffsetSeconds INTEGER NOT NULL, chatUrl TEXT, playlistToFile INTEGER NOT NULL, live INTEGER NOT NULL, lastSegmentUrl TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") - db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, bytes, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, live, id) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, 0, id FROM videos") - db.execSQL("DROP TABLE videos") - db.execSQL("ALTER TABLE videos1 RENAME TO videos") - } - }, - object : Migration(26, 27) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS shown_notifications (channelId TEXT NOT NULL, startedAt INTEGER NOT NULL, PRIMARY KEY (channelId))") - } - }, - object : Migration(27, 28) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("CREATE TABLE IF NOT EXISTS notifications (channelId TEXT NOT NULL, PRIMARY KEY (channelId))") - } - }, - ) - .build() + Room.databaseBuilder(application, AppDatabase::class.java, "database") + .addMigrations( + object : Migration(9, 10) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DELETE FROM emotes") + } + }, + object : Migration(10, 11) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE videos ADD COLUMN videoId TEXT DEFAULT null") + } + }, + object : Migration(11, 12) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games (game_id TEXT NOT NULL, game_name TEXT, boxArt TEXT, PRIMARY KEY (game_id))") + } + }, + object : Migration(12, 13) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT NOT NULL, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER NOT NULL, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + } + }, + object : Migration(13, 14) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER, type TEXT, videoId TEXT, is_bookmark INTEGER, userType TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id = id, is_vod = is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + db.execSQL("CREATE TABLE IF NOT EXISTS vod_bookmark_ignored_users (user_id TEXT NOT NULL, PRIMARY KEY (user_id))") + } + }, + object : Migration(14, 15) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT OR IGNORE INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id = id, is_vod = is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks (id TEXT NOT NULL, userId TEXT, userLogin TEXT, userName TEXT, userLogo TEXT, gameId TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, PRIMARY KEY (id))") + } + }, + object : Migration(15, 16) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE bookmarks ADD COLUMN userType TEXT DEFAULT null") + db.execSQL("ALTER TABLE bookmarks ADD COLUMN userBroadcasterType TEXT DEFAULT null") + } + }, + object : Migration(16, 17) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS sort_channel (id TEXT NOT NULL, saveSort INTEGER, videoSort TEXT, videoType TEXT, clipPeriod TEXT, PRIMARY KEY (id))") + db.execSQL("CREATE TABLE IF NOT EXISTS sort_game (id TEXT NOT NULL, saveSort INTEGER, videoSort TEXT, videoPeriod TEXT, videoType TEXT, videoLanguageIndex INTEGER, clipPeriod TEXT, clipLanguageIndex INTEGER, PRIMARY KEY (id))") + } + }, + object : Migration(17, 18) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS recent_emotes1 (name TEXT NOT NULL, url1x TEXT, url2x TEXT, url3x TEXT, url4x TEXT, used_at INTEGER NOT NULL, PRIMARY KEY (name))") + db.execSQL("INSERT INTO recent_emotes1 (name, url1x, used_at) SELECT name, url, used_at FROM recent_emotes") + db.execSQL("DROP TABLE recent_emotes") + db.execSQL("ALTER TABLE recent_emotes1 RENAME TO recent_emotes") + } + }, + object : Migration(18, 19) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks1 (videoId TEXT, userId TEXT, userLogin TEXT, userName TEXT, userType TEXT, userBroadcasterType TEXT, userLogo TEXT, gameId TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, animatedPreviewURL TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO bookmarks1 (videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration) SELECT id, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration FROM bookmarks") + db.execSQL("DROP TABLE bookmarks") + db.execSQL("ALTER TABLE bookmarks1 RENAME TO bookmarks") + db.execSQL("CREATE TABLE IF NOT EXISTS local_follows1 (userId TEXT, userLogin TEXT, userName TEXT, channelLogo TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO local_follows1 (userId, userLogin, userName, channelLogo) SELECT user_id, user_login, user_name, channelLogo FROM local_follows") + db.execSQL("DROP TABLE local_follows") + db.execSQL("ALTER TABLE local_follows1 RENAME TO local_follows") + db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games1 (gameId TEXT, gameName TEXT, boxArt TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO local_follows_games1 (gameId, gameName, boxArt) SELECT game_id, game_name, boxArt FROM local_follows_games") + db.execSQL("DROP TABLE local_follows_games") + db.execSQL("ALTER TABLE local_follows_games1 RENAME TO local_follows_games") + db.execSQL("CREATE TABLE IF NOT EXISTS requests1 (offline_video_id INTEGER NOT NULL, url TEXT NOT NULL, path TEXT NOT NULL, video_id TEXT, video_type TEXT, segment_from INTEGER, segment_to INTEGER, PRIMARY KEY (offline_video_id), FOREIGN KEY('offline_video_id') REFERENCES videos('id') ON DELETE CASCADE)") + db.execSQL("INSERT INTO requests1 (offline_video_id, url, path, video_id, segment_from, segment_to) SELECT offline_video_id, url, path, video_id, segment_from, segment_to FROM requests") + db.execSQL("DROP TABLE requests") + db.execSQL("ALTER TABLE requests1 RENAME TO requests") + } + }, + object : Migration(19, 20) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS recent_emotes1 (name TEXT NOT NULL, used_at INTEGER NOT NULL, PRIMARY KEY (name))") + db.execSQL("INSERT INTO recent_emotes1 (name, used_at) SELECT name, used_at FROM recent_emotes") + db.execSQL("DROP TABLE recent_emotes") + db.execSQL("ALTER TABLE recent_emotes1 RENAME TO recent_emotes") + } + }, + object : Migration(20, 21) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS requests1 (offline_video_id INTEGER NOT NULL, url TEXT NOT NULL, path TEXT NOT NULL, PRIMARY KEY (offline_video_id), FOREIGN KEY('offline_video_id') REFERENCES videos('id') ON DELETE CASCADE)") + db.execSQL("INSERT INTO requests1 (offline_video_id, url, path) SELECT offline_video_id, url, path FROM requests") + db.execSQL("DROP TABLE requests") + db.execSQL("ALTER TABLE requests1 RENAME TO requests") + } + }, + object : Migration(21, 22) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, status INTEGER NOT NULL, type TEXT, videoId TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + db.execSQL("CREATE TABLE IF NOT EXISTS bookmarks1 (videoId TEXT, userId TEXT, userLogin TEXT, userName TEXT, userType TEXT, userBroadcasterType TEXT, userLogo TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, title TEXT, createdAt TEXT, thumbnail TEXT, type TEXT, duration TEXT, animatedPreviewURL TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO bookmarks1 (videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration, animatedPreviewURL, id) SELECT videoId, userId, userLogin, userName, userType, userBroadcasterType, userLogo, gameId, gameName, title, createdAt, thumbnail, type, duration, animatedPreviewURL, id FROM bookmarks") + db.execSQL("DROP TABLE bookmarks") + db.execSQL("ALTER TABLE bookmarks1 RENAME TO bookmarks") + db.execSQL("CREATE TABLE IF NOT EXISTS local_follows_games1 (gameId TEXT, gameSlug TEXT, gameName TEXT, boxArt TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO local_follows_games1 (gameId, gameName, boxArt, id) SELECT gameId, gameName, boxArt, id FROM local_follows_games") + db.execSQL("DROP TABLE local_follows_games") + db.execSQL("ALTER TABLE local_follows_games1 RENAME TO local_follows_games") + } + }, + object : Migration(22, 23) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, quality TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, status, type, videoId, id, is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + } + }, + object : Migration(23, 24) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT NOT NULL, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, quality TEXT, downloadChat INTEGER, downloadChatEmotes INTEGER, chatProgress INTEGER, chatUrl TEXT, id INTEGER NOT NULL, is_vod INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, id, is_vod) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, id, is_vod FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + } + }, + object : Migration(24, 25) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE requests") + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, bytes INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, clipId TEXT, quality TEXT, downloadChat INTEGER NOT NULL, downloadChatEmotes INTEGER NOT NULL, chatProgress INTEGER NOT NULL, maxChatProgress INTEGER NOT NULL, chatBytes INTEGER NOT NULL, chatOffsetSeconds INTEGER NOT NULL, chatUrl TEXT, playlistToFile INTEGER NOT NULL, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, bytes, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, id) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, 0, downloadPath, fromTime, toTime, status, type, videoId, quality, 0, 0, 0, 100, 0, 0, chatUrl, 0, id FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + } + }, + object : Migration(25, 26) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS videos1 (url TEXT, source_url TEXT, source_start_position INTEGER, name TEXT, channel_id TEXT, channel_login TEXT, channel_name TEXT, channel_logo TEXT, thumbnail TEXT, gameId TEXT, gameSlug TEXT, gameName TEXT, duration INTEGER, upload_date INTEGER, download_date INTEGER, last_watch_position INTEGER, progress INTEGER NOT NULL, max_progress INTEGER NOT NULL, bytes INTEGER NOT NULL, downloadPath TEXT, fromTime INTEGER, toTime INTEGER, status INTEGER NOT NULL, type TEXT, videoId TEXT, clipId TEXT, quality TEXT, downloadChat INTEGER NOT NULL, downloadChatEmotes INTEGER NOT NULL, chatProgress INTEGER NOT NULL, maxChatProgress INTEGER NOT NULL, chatBytes INTEGER NOT NULL, chatOffsetSeconds INTEGER NOT NULL, chatUrl TEXT, playlistToFile INTEGER NOT NULL, live INTEGER NOT NULL, lastSegmentUrl TEXT, id INTEGER NOT NULL, PRIMARY KEY (id))") + db.execSQL("INSERT INTO videos1 (url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, bytes, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, live, id) SELECT url, source_url, source_start_position, name, channel_id, channel_login, channel_name, channel_logo, thumbnail, gameId, gameSlug, gameName, duration, upload_date, download_date, last_watch_position, progress, max_progress, max_progress, downloadPath, fromTime, toTime, status, type, videoId, quality, downloadChat, downloadChatEmotes, chatProgress, maxChatProgress, chatBytes, chatOffsetSeconds, chatUrl, playlistToFile, 0, id FROM videos") + db.execSQL("DROP TABLE videos") + db.execSQL("ALTER TABLE videos1 RENAME TO videos") + } + }, + object : Migration(26, 27) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS shown_notifications (channelId TEXT NOT NULL, startedAt INTEGER NOT NULL, PRIMARY KEY (channelId))") + } + }, + object : Migration(27, 28) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE TABLE IF NOT EXISTS notifications (channelId TEXT NOT NULL, PRIMARY KEY (channelId))") + } + }, + ) + .build() } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/di/XtraModule.kt b/app/src/main/java/com/github/andreyasadchy/xtra/di/XtraModule.kt index 7d95ba15a..222d2e49c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/di/XtraModule.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/di/XtraModule.kt @@ -46,55 +46,55 @@ class XtraModule { @Provides fun providesHelixApi(client: OkHttpClient, jsonConverterFactory: Converter.Factory): HelixApi { return Retrofit.Builder() - .baseUrl("https://api.twitch.tv/helix/") - .client(client) - .addConverterFactory(jsonConverterFactory) - .build() - .create(HelixApi::class.java) + .baseUrl("https://api.twitch.tv/helix/") + .client(client) + .addConverterFactory(jsonConverterFactory) + .build() + .create(HelixApi::class.java) } @Singleton @Provides fun providesUsherApi(client: OkHttpClient, jsonConverterFactory: Converter.Factory): UsherApi { return Retrofit.Builder() - .baseUrl("https://usher.ttvnw.net/") - .client(client) - .addConverterFactory(jsonConverterFactory) - .build() - .create(UsherApi::class.java) + .baseUrl("https://usher.ttvnw.net/") + .client(client) + .addConverterFactory(jsonConverterFactory) + .build() + .create(UsherApi::class.java) } @Singleton @Provides fun providesMiscApi(client: OkHttpClient, jsonConverterFactory: Converter.Factory): MiscApi { return Retrofit.Builder() - .baseUrl("https://api.twitch.tv/") //placeholder url - .client(client) - .addConverterFactory(jsonConverterFactory) - .build() - .create(MiscApi::class.java) + .baseUrl("https://api.twitch.tv/") //placeholder url + .client(client) + .addConverterFactory(jsonConverterFactory) + .build() + .create(MiscApi::class.java) } @Singleton @Provides fun providesIdApi(client: OkHttpClient, jsonConverterFactory: Converter.Factory): IdApi { return Retrofit.Builder() - .baseUrl("https://id.twitch.tv/oauth2/") - .client(client) - .addConverterFactory(jsonConverterFactory) - .build() - .create(IdApi::class.java) + .baseUrl("https://id.twitch.tv/oauth2/") + .client(client) + .addConverterFactory(jsonConverterFactory) + .build() + .create(IdApi::class.java) } @Singleton @Provides fun providesGraphQLApi(client: OkHttpClient, jsonConverterFactory: Converter.Factory): GraphQLApi { return Retrofit.Builder() - .baseUrl("https://gql.twitch.tv/gql/") - .client(client) - .addConverterFactory(jsonConverterFactory) - .build() - .create(GraphQLApi::class.java) + .baseUrl("https://gql.twitch.tv/gql/") + .client(client) + .addConverterFactory(jsonConverterFactory) + .build() + .create(GraphQLApi::class.java) } @Singleton diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/NotificationUser.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/NotificationUser.kt index 12b0606f7..fbbd1b9d9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/NotificationUser.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/NotificationUser.kt @@ -6,4 +6,5 @@ import androidx.room.PrimaryKey @Entity(tableName = "notifications") class NotificationUser( @PrimaryKey - val channelId: String) \ No newline at end of file + val channelId: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ShownNotification.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ShownNotification.kt index 84937a414..7e3e075f9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ShownNotification.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ShownNotification.kt @@ -7,4 +7,5 @@ import androidx.room.PrimaryKey class ShownNotification( @PrimaryKey val channelId: String, - val startedAt: Long) \ No newline at end of file + val startedAt: Long, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/VideoPosition.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/VideoPosition.kt index e7e7d447b..b640c1b7a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/VideoPosition.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/VideoPosition.kt @@ -7,4 +7,5 @@ import androidx.room.PrimaryKey class VideoPosition( @PrimaryKey val id: Long, - val position: Long) \ No newline at end of file + val position: Long, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Badge.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Badge.kt index 0629f994c..32313d5a1 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Badge.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Badge.kt @@ -2,4 +2,5 @@ package com.github.andreyasadchy.xtra.model.chat class Badge( val setId: String, - val version: String) + val version: String, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChannelPointReward.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChannelPointReward.kt index 12839cf8c..11abfef4d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChannelPointReward.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChannelPointReward.kt @@ -6,4 +6,5 @@ class ChannelPointReward( val cost: Int? = null, val url1x: String? = null, val url2x: String? = null, - val url4x: String? = null) \ No newline at end of file + val url4x: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChatMessage.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChatMessage.kt index 5d5a3ac7d..2906617bb 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChatMessage.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/ChatMessage.kt @@ -17,4 +17,5 @@ class ChatMessage( val reward: ChannelPointReward? = null, val reply: Reply? = null, val timestamp: Long? = null, - val fullMsg: String? = null) \ No newline at end of file + val fullMsg: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Chatter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Chatter.kt index 8fab8fa01..a6e087d4b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Chatter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Chatter.kt @@ -9,9 +9,7 @@ class Chatter(val name: String?) { other as Chatter - if (name != other.name) return false - - return true + return name == other.name } override fun hashCode(): Int { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/CheerEmote.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/CheerEmote.kt index 6a9ec2290..0c11fe598 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/CheerEmote.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/CheerEmote.kt @@ -10,4 +10,5 @@ class CheerEmote( val format: String? = null, val isAnimated: Boolean = true, val minBits: Int, - val color: String? = null) \ No newline at end of file + val color: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Emote.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Emote.kt index 761e15e1b..aef73f563 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Emote.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Emote.kt @@ -9,7 +9,8 @@ class Emote( val url4x: String? = null, val format: String? = null, val isAnimated: Boolean = true, - val isZeroWidth: Boolean = false) { + val isZeroWidth: Boolean = false, +) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/EmoteCard.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/EmoteCard.kt index 993cb0ae4..85061b1ff 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/EmoteCard.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/EmoteCard.kt @@ -5,4 +5,5 @@ class EmoteCard( val subTier: String?, val bitThreshold: Int?, val channelLogin: String?, - val channelName: String?) + val channelName: String?, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Image.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Image.kt index d6c788a6c..37d981acc 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Image.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Image.kt @@ -11,4 +11,5 @@ class Image( val isZeroWidth: Boolean = false, val isEmote: Boolean = false, var start: Int, - var end: Int) \ No newline at end of file + var end: Int, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/NamePaint.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/NamePaint.kt index 18dd04dde..4361614e3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/NamePaint.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/NamePaint.kt @@ -8,12 +8,13 @@ class NamePaint( val imageUrl: String? = null, val angle: Int? = null, val repeat: Boolean? = null, - val shadows: List? = null) { + val shadows: List? = null, +) { class Shadow( val xOffset: Float, val yOffset: Float, val radius: Float, - val color: Int + val color: Int, ) } \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Raid.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Raid.kt index 8f955c1da..5a41bfa30 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Raid.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Raid.kt @@ -9,7 +9,8 @@ class Raid( val targetName: String? = null, val targetProfileImage: String? = null, val viewerCount: Int? = null, - val openStream: Boolean) { + val openStream: Boolean, +) { val targetLogo: String? get() = TwitchApiHelper.getTemplateUrl(targetProfileImage, "profileimage") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RecentEmote.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RecentEmote.kt index c689a8255..16847d6bc 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RecentEmote.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RecentEmote.kt @@ -9,7 +9,8 @@ class RecentEmote( @PrimaryKey val name: String, @ColumnInfo(name = "used_at") - val usedAt: Long) { + val usedAt: Long, +) { companion object { const val MAX_SIZE = 50 diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Reply.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Reply.kt index b5a1355dc..7f118a0ed 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Reply.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/Reply.kt @@ -4,4 +4,5 @@ class Reply( val threadParentId: String? = null, val userLogin: String? = null, val userName: String? = null, - val message: String? = null) \ No newline at end of file + val message: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RoomState.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RoomState.kt index 3cd7867c8..d1ddcf47e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RoomState.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/RoomState.kt @@ -5,4 +5,5 @@ class RoomState( val followers: String?, val unique: String?, val slow: String?, - val subs: String?) \ No newline at end of file + val subs: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/StvBadge.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/StvBadge.kt index c8bcc53ac..c32902379 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/StvBadge.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/StvBadge.kt @@ -7,4 +7,5 @@ class StvBadge( val url3x: String?, val url4x: String?, val name: String?, - val format: String?) + val format: String?, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchBadge.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchBadge.kt index 547867f3d..e16405fa7 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchBadge.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchBadge.kt @@ -8,4 +8,5 @@ class TwitchBadge( val url2x: String? = null, val url3x: String? = null, val url4x: String? = null, - val title: String? = null) + val title: String? = null, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchEmote.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchEmote.kt index 275574cc2..aa7fc2229 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchEmote.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/TwitchEmote.kt @@ -13,4 +13,5 @@ class TwitchEmote( var begin: Int = 0, var end: Int = 0, val setId: String? = null, - val ownerId: String? = null) + val ownerId: String? = null, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/VideoChatMessage.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/VideoChatMessage.kt index 63a32c9e1..495a06eee 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/VideoChatMessage.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/chat/VideoChatMessage.kt @@ -10,4 +10,5 @@ class VideoChatMessage( val color: String?, val emotes: List?, val badges: List?, - val fullMsg: String?) \ No newline at end of file + val fullMsg: String?, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/DeviceCodeResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/DeviceCodeResponse.kt index f6d96e5f7..9af284e6f 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/DeviceCodeResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/DeviceCodeResponse.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable @Serializable class DeviceCodeResponse( @SerialName("device_code") - val deviceCode: String + val deviceCode: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/TokenResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/TokenResponse.kt index 938d421b1..33b2faba4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/TokenResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/TokenResponse.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable @Serializable class TokenResponse( @SerialName("access_token") - val token: String + val token: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/ValidationResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/ValidationResponse.kt index b257784b3..a1cea6eff 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/id/ValidationResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/id/ValidationResponse.kt @@ -9,5 +9,5 @@ class ValidationResponse( val clientId: String, val login: String? = null, @SerialName("user_id") - val userId: String? = null + val userId: String? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/RecentMessagesResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/RecentMessagesResponse.kt index b005674dc..5ae08201d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/RecentMessagesResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/RecentMessagesResponse.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable class RecentMessagesResponse( - val messages: List + val messages: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvChannelResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvChannelResponse.kt index 4dda5d5b3..773619330 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvChannelResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvChannelResponse.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable @Serializable class StvChannelResponse( @SerialName("emote_set") - val emoteSet: StvGlobalResponse + val emoteSet: StvGlobalResponse, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvGlobalResponse.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvGlobalResponse.kt index 5aac1b08d..23249f0e6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvGlobalResponse.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/misc/StvGlobalResponse.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable class StvGlobalResponse( val id: String? = null, - val emotes: List + val emotes: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Bookmark.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Bookmark.kt index c67bb7af2..9c951dce3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Bookmark.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Bookmark.kt @@ -20,7 +20,8 @@ class Bookmark( val thumbnail: String? = null, val type: String? = null, val duration: String? = null, - val animatedPreviewURL: String? = null) { + val animatedPreviewURL: String? = null, +) { @PrimaryKey(autoGenerate = true) var id = 0 diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/ChannelViewerList.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/ChannelViewerList.kt index fc9e22252..3ae78cb4c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/ChannelViewerList.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/ChannelViewerList.kt @@ -5,4 +5,5 @@ class ChannelViewerList( val moderators: List, val vips: List, val viewers: List, - val count: Int?) \ No newline at end of file + val count: Int?, +) \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Clip.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Clip.kt index 4f5422e3c..e6419e02e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Clip.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Clip.kt @@ -22,7 +22,8 @@ class Clip( var gameName: String? = null, var channelLogin: String? = null, var profileImageUrl: String? = null, - val videoAnimatedPreviewURL: String? = null) : Parcelable { + val videoAnimatedPreviewURL: String? = null, +) : Parcelable { val thumbnail: String? get() = TwitchApiHelper.getTemplateUrl(thumbnailUrl, "clip") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Game.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Game.kt index 4907286c5..034925c25 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Game.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Game.kt @@ -18,7 +18,8 @@ class Game( val vodDuration: Int? = null, var followAccount: Boolean = false, - val followLocal: Boolean = false) : Parcelable { + val followLocal: Boolean = false, +) : Parcelable { val boxArt: String? get() = TwitchApiHelper.getTemplateUrl(boxArtUrl, "game") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowChannel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowChannel.kt index 669b8a4b4..08d1cb947 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowChannel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowChannel.kt @@ -8,7 +8,8 @@ class LocalFollowChannel( val userId: String? = null, var userLogin: String? = null, var userName: String? = null, - var channelLogo: String? = null) { + var channelLogo: String? = null, +) { @PrimaryKey(autoGenerate = true) var id = 0 diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowGame.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowGame.kt index b233878cd..b7fb5b0c4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowGame.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/LocalFollowGame.kt @@ -8,7 +8,8 @@ class LocalFollowGame( val gameId: String? = null, val gameSlug: String? = null, var gameName: String? = null, - var boxArt: String? = null) { + var boxArt: String? = null, +) { @PrimaryKey(autoGenerate = true) var id = 0 diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/OfflineVideo.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/OfflineVideo.kt index d3ef2d57e..74b5de7c3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/OfflineVideo.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/OfflineVideo.kt @@ -56,7 +56,8 @@ class OfflineVideo( var chatUrl: String? = null, val playlistToFile: Boolean = false, val live: Boolean = false, - var lastSegmentUrl: String? = null) : Parcelable { + var lastSegmentUrl: String? = null, +) : Parcelable { @IgnoredOnParcel @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortChannel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortChannel.kt index e1afc22b5..0ac9f6940 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortChannel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortChannel.kt @@ -10,4 +10,5 @@ class SortChannel( var saveSort: Boolean? = null, var videoSort: String? = null, var videoType: String? = null, - var clipPeriod: String? = null) + var clipPeriod: String? = null, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortGame.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortGame.kt index 3d59a6112..7be582637 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortGame.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/SortGame.kt @@ -13,4 +13,5 @@ class SortGame( var videoType: String? = null, var videoLanguageIndex: Int? = null, var clipPeriod: String? = null, - var clipLanguageIndex: Int? = null) + var clipLanguageIndex: Int? = null, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Stream.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Stream.kt index 345585878..90b0598f2 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Stream.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Stream.kt @@ -21,7 +21,8 @@ class Stream( var profileImageUrl: String? = null, val tags: List? = null, - val user: User? = null) : Parcelable { + val user: User? = null, +) : Parcelable { val thumbnail: String? get() = TwitchApiHelper.getTemplateUrl(thumbnailUrl, "video") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Tag.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Tag.kt index 813cc94ff..e28a292dd 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Tag.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Tag.kt @@ -7,4 +7,5 @@ import kotlinx.parcelize.Parcelize class Tag( val id: String? = null, val name: String? = null, - val scope: String? = null) : Parcelable \ No newline at end of file + val scope: String? = null, +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/User.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/User.kt index c6bd22a31..f2cf9dfbe 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/User.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/User.kt @@ -22,7 +22,8 @@ class User( val stream: Stream? = null, var followAccount: Boolean = false, - val followLocal: Boolean = false) : Parcelable { + val followLocal: Boolean = false, +) : Parcelable { val channelLogo: String? get() = TwitchApiHelper.getTemplateUrl(profileImageUrl, "profileimage") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Video.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Video.kt index b7d1d387a..31cc541f2 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Video.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/Video.kt @@ -22,7 +22,8 @@ class Video( var gameName: String? = null, var profileImageUrl: String? = null, val tags: List? = null, - val animatedPreviewURL: String? = null) : Parcelable { + val animatedPreviewURL: String? = null, +) : Parcelable { val thumbnail: String? get() = TwitchApiHelper.getTemplateUrl(thumbnailUrl, "video") diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/VodBookmarkIgnoredUser.kt b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/VodBookmarkIgnoredUser.kt index 7160d5f23..5025e0058 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/VodBookmarkIgnoredUser.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/model/ui/VodBookmarkIgnoredUser.kt @@ -8,4 +8,5 @@ import androidx.room.PrimaryKey class VodBookmarkIgnoredUser( @PrimaryKey @ColumnInfo(name = "user_id") - val userId: String) + val userId: String, +) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/ApiRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/ApiRepository.kt index af96eecf3..64654bc73 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/ApiRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/ApiRepository.kt @@ -48,7 +48,8 @@ class ApiRepository @Inject constructor( private val apolloClient: ApolloClient, private val helix: HelixApi, private val gql: GraphQLRepository, - private val misc: MiscApi) { + private val misc: MiscApi, +) { private fun getApolloClient(gqlHeaders: Map): ApolloClient { return apolloClient.newBuilder().apply { @@ -153,7 +154,7 @@ class ApiRepository @Inject constructor( duration = it.lengthSeconds?.toString(), thumbnailUrl = it.previewThumbnailURL, profileImageUrl = it.owner?.profileImageURL, - animatedPreviewURL = it.animatedPreviewURL, + animatedPreviewURL = it.animatedPreviewURL, ) } } @@ -448,28 +449,33 @@ class ApiRepository @Inject constructor( suspend fun loadGlobalBadges(helixHeaders: Map, gqlHeaders: Map, emoteQuality: String, checkIntegrity: Boolean): List = withContext(Dispatchers.IO) { try { - val response = getApolloClient(gqlHeaders).query(BadgesQuery(Optional.Present(when (emoteQuality) {"4" -> BadgeImageSize.QUADRUPLE "3" -> BadgeImageSize.QUADRUPLE "2" -> BadgeImageSize.DOUBLE else -> BadgeImageSize.NORMAL}))).execute() + val response = getApolloClient(gqlHeaders).query(BadgesQuery(Optional.Present( + when (emoteQuality) { + "4" -> BadgeImageSize.QUADRUPLE + "3" -> BadgeImageSize.QUADRUPLE + "2" -> BadgeImageSize.DOUBLE + else -> BadgeImageSize.NORMAL + } + ))).execute() if (checkIntegrity) { response.errors?.find { it.message == "failed integrity check" }?.let { throw Exception(it.message) } } response.data!!.badges?.mapNotNull { - if (it != null) { - it.setID?.let { setId -> - it.version?.let { version -> - it.imageURL?.let { url -> - TwitchBadge( - setId = setId, - version = version, - url1x = url, - url2x = url, - url3x = url, - url4x = url, - title = it.title - ) - } + it?.setID?.let { setId -> + it.version?.let { version -> + it.imageURL?.let { url -> + TwitchBadge( + setId = setId, + version = version, + url1x = url, + url2x = url, + url3x = url, + url4x = url, + title = it.title + ) } } - } else null + } } ?: emptyList() } catch (e: Exception) { if (e.message == "failed integrity check") throw e @@ -522,29 +528,34 @@ class ApiRepository @Inject constructor( val response = getApolloClient(gqlHeaders).query(UserBadgesQuery( id = if (!channelId.isNullOrBlank()) Optional.Present(channelId) else Optional.Absent, login = if (channelId.isNullOrBlank() && !channelLogin.isNullOrBlank()) Optional.Present(channelLogin) else Optional.Absent, - quality = Optional.Present(when (emoteQuality) {"4" -> BadgeImageSize.QUADRUPLE "3" -> BadgeImageSize.QUADRUPLE "2" -> BadgeImageSize.DOUBLE else -> BadgeImageSize.NORMAL}) + quality = Optional.Present( + when (emoteQuality) { + "4" -> BadgeImageSize.QUADRUPLE + "3" -> BadgeImageSize.QUADRUPLE + "2" -> BadgeImageSize.DOUBLE + else -> BadgeImageSize.NORMAL + } + ) )).execute() if (checkIntegrity) { response.errors?.find { it.message == "failed integrity check" }?.let { throw Exception(it.message) } } response.data!!.user?.broadcastBadges?.mapNotNull { - if (it != null) { - it.setID?.let { setId -> - it.version?.let { version -> - it.imageURL?.let { url -> - TwitchBadge( - setId = setId, - version = version, - url1x = url, - url2x = url, - url3x = url, - url4x = url, - title = it.title - ) - } + it?.setID?.let { setId -> + it.version?.let { version -> + it.imageURL?.let { url -> + TwitchBadge( + setId = setId, + version = version, + url1x = url, + url2x = url, + url3x = url, + url4x = url, + title = it.title + ) } } - } else null + } } ?: emptyList() } catch (e: Exception) { if (e.message == "failed integrity check") throw e diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/AuthRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/AuthRepository.kt index f3dbbb613..c95fc8da5 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/AuthRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/AuthRepository.kt @@ -12,7 +12,8 @@ import javax.inject.Singleton @Singleton class AuthRepository @Inject constructor( - private val api: IdApi) { + private val api: IdApi, +) { suspend fun validate(token: String): ValidationResponse = withContext(Dispatchers.IO) { api.validateToken(token) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/BookmarksRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/BookmarksRepository.kt index 2ada4bd39..c8c9f42ab 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/BookmarksRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/BookmarksRepository.kt @@ -14,7 +14,8 @@ import javax.inject.Singleton class BookmarksRepository @Inject constructor( private val bookmarksDao: BookmarksDao, private val localFollowsChannelDao: LocalFollowsChannelDao, - private val videosDao: VideosDao) { + private val videosDao: VideosDao, +) { fun loadBookmarksPagingSource() = bookmarksDao.getAllPagingSource() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/GraphQLRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/GraphQLRepository.kt index cf81f4a94..c3b8e13e3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/GraphQLRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/GraphQLRepository.kt @@ -49,7 +49,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class GraphQLRepository @Inject constructor(private val graphQL: GraphQLApi) { +class GraphQLRepository @Inject constructor( + private val graphQL: GraphQLApi, +) { fun getPlaybackAccessTokenRequestBody(login: String?, vodId: String?, playerType: String?): JsonObject { return buildJsonObject { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowChannelRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowChannelRepository.kt index b6b8879f6..2a42c23e5 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowChannelRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowChannelRepository.kt @@ -14,7 +14,8 @@ import javax.inject.Singleton class LocalFollowChannelRepository @Inject constructor( private val localFollowsChannelDao: LocalFollowsChannelDao, private val videosDao: VideosDao, - private val bookmarksDao: BookmarksDao) { + private val bookmarksDao: BookmarksDao, +) { suspend fun loadFollows() = withContext(Dispatchers.IO) { localFollowsChannelDao.getAll() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowGameRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowGameRepository.kt index 84f5f8fed..8ac759c4c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowGameRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/LocalFollowGameRepository.kt @@ -10,7 +10,8 @@ import javax.inject.Singleton @Singleton class LocalFollowGameRepository @Inject constructor( - private val localFollowsGameDao: LocalFollowsGameDao) { + private val localFollowsGameDao: LocalFollowsGameDao, +) { suspend fun loadFollows() = withContext(Dispatchers.IO) { localFollowsGameDao.getAll() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/NotificationUsersRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/NotificationUsersRepository.kt index 7c889eca4..c2e1a43e9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/NotificationUsersRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/NotificationUsersRepository.kt @@ -9,7 +9,8 @@ import javax.inject.Singleton @Singleton class NotificationUsersRepository @Inject constructor( - private val notificationUsersDao: NotificationUsersDao) { + private val notificationUsersDao: NotificationUsersDao, +) { suspend fun loadUsers() = withContext(Dispatchers.IO) { notificationUsersDao.getAll() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/OfflineRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/OfflineRepository.kt index ba00620b7..78e352f9d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/OfflineRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/OfflineRepository.kt @@ -12,9 +12,10 @@ import javax.inject.Singleton @Singleton class OfflineRepository @Inject constructor( - private val videosDao: VideosDao, - private val localFollowsChannelDao: LocalFollowsChannelDao, - private val bookmarksDao: BookmarksDao) { + private val videosDao: VideosDao, + private val localFollowsChannelDao: LocalFollowsChannelDao, + private val bookmarksDao: BookmarksDao, +) { fun loadAllVideos() = videosDao.getAll() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/PlayerRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/PlayerRepository.kt index 98c7f4c12..c29968baf 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/PlayerRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/PlayerRepository.kt @@ -49,7 +49,8 @@ class PlayerRepository @Inject constructor( private val misc: MiscApi, private val graphQL: GraphQLRepository, private val recentEmotes: RecentEmotesDao, - private val videoPositions: VideoPositionsDao) { + private val videoPositions: VideoPositionsDao, +) { suspend fun getMediaPlaylist(url: String): MediaPlaylist = withContext(Dispatchers.IO) { okHttpClient.newCall(Request.Builder().url(url).build()).execute().use { response -> @@ -270,7 +271,7 @@ class PlayerRepository @Inject constructor( url3x = urls?.getOrNull(2) ?: if (urls.isNullOrEmpty()) "https:${template}/3x.webp" else null, url4x = urls?.getOrNull(3) ?: if (urls.isNullOrEmpty()) "https:${template}/4x.webp" else null, format = urls?.getOrNull(0)?.substringAfterLast(".") ?: "webp", - isAnimated = data.animated ?: true, + isAnimated = data.animated != false, isZeroWidth = emote.flags == 1 ) } @@ -321,7 +322,7 @@ class PlayerRepository @Inject constructor( url3x = "https://cdn.betterttv.net/emote/$id/2x.webp", url4x = "https://cdn.betterttv.net/emote/$id/3x.webp", format = "webp", - isAnimated = emote.animated ?: true, + isAnimated = emote.animated != false, isZeroWidth = list.contains(name) ) } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/ShownNotificationsRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/ShownNotificationsRepository.kt index ba89a3e25..f306600ab 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/ShownNotificationsRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/ShownNotificationsRepository.kt @@ -17,7 +17,8 @@ import javax.inject.Singleton @Singleton class ShownNotificationsRepository @Inject constructor( - private val shownNotificationsDao: ShownNotificationsDao) { + private val shownNotificationsDao: ShownNotificationsDao, +) { suspend fun getNewStreams(notificationUsersRepository: NotificationUsersRepository, gqlHeaders: Map, apolloClient: ApolloClient, helixHeaders: Map, helixApi: HelixApi): List = withContext(Dispatchers.IO) { val list = mutableListOf() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortChannelRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortChannelRepository.kt index c2556dbfc..cee04c087 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortChannelRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortChannelRepository.kt @@ -9,7 +9,8 @@ import javax.inject.Singleton @Singleton class SortChannelRepository @Inject constructor( - private val sortChannelDao: SortChannelDao) { + private val sortChannelDao: SortChannelDao, +) { suspend fun getById(id: String) = withContext(Dispatchers.IO) { sortChannelDao.getById(id) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortGameRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortGameRepository.kt index 56c4b677f..9263c7ab6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortGameRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/SortGameRepository.kt @@ -9,7 +9,8 @@ import javax.inject.Singleton @Singleton class SortGameRepository @Inject constructor( - private val sortGameDao: SortGameDao) { + private val sortGameDao: SortGameDao, +) { suspend fun getById(id: String) = withContext(Dispatchers.IO) { sortGameDao.getById(id) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/VodBookmarkIgnoredUsersRepository.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/VodBookmarkIgnoredUsersRepository.kt index 2f2e02aa9..219ff7452 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/VodBookmarkIgnoredUsersRepository.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/VodBookmarkIgnoredUsersRepository.kt @@ -9,7 +9,8 @@ import javax.inject.Singleton @Singleton class VodBookmarkIgnoredUsersRepository @Inject constructor( - private val vodBookmarkIgnoredUsersDao: VodBookmarkIgnoredUsersDao) { + private val vodBookmarkIgnoredUsersDao: VodBookmarkIgnoredUsersDao, +) { fun loadUsersFlow() = vodBookmarkIgnoredUsersDao.getAllFlow() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelClipsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelClipsDataSource.kt index a0ed13c36..298c8c06e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelClipsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelClipsDataSource.kt @@ -24,7 +24,8 @@ class ChannelClipsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -151,7 +152,7 @@ class ChannelClipsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.clips.pageInfo?.hasNextPage ?: true + nextPage = data.clips.pageInfo?.hasNextPage != false return list } @@ -181,7 +182,7 @@ class ChannelClipsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.clips.pageInfo?.hasNextPage ?: true + nextPage = data.clips.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelVideosDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelVideosDataSource.kt index 3f83b2ba0..efc6a9d24 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelVideosDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/ChannelVideosDataSource.kt @@ -29,7 +29,8 @@ class ChannelVideosDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -146,12 +147,12 @@ class ChannelVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.videos.pageInfo?.hasNextPage ?: true + nextPage = data.videos.pageInfo?.hasNextPage != false return list } @@ -183,12 +184,12 @@ class ChannelVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor - nextPage = data.videos.pageInfo?.hasNextPage ?: true + nextPage = data.videos.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedChannelsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedChannelsDataSource.kt index 88025ee60..60b1b7373 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedChannelsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedChannelsDataSource.kt @@ -39,7 +39,8 @@ class FollowedChannelsDataSource( private val checkIntegrity: Boolean, private val apiPref: List, private val sort: String, - private val order: String) : PagingSource() { + private val order: String, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -222,7 +223,7 @@ class FollowedChannelsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -245,7 +246,7 @@ class FollowedChannelsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedGamesDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedGamesDataSource.kt index 1eafc955b..8452d3bac 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedGamesDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedGamesDataSource.kt @@ -17,7 +17,8 @@ class FollowedGamesDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedStreamsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedStreamsDataSource.kt index ede0e4eb6..124681bf3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedStreamsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedStreamsDataSource.kt @@ -21,7 +21,8 @@ class FollowedStreamsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -177,7 +178,7 @@ class FollowedStreamsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -207,7 +208,7 @@ class FollowedStreamsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedVideosDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedVideosDataSource.kt index b62129d49..f052cf324 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedVideosDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/FollowedVideosDataSource.kt @@ -19,7 +19,8 @@ class FollowedVideosDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -95,12 +96,12 @@ class FollowedVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -132,12 +133,12 @@ class FollowedVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameClipsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameClipsDataSource.kt index b6e63e75f..7398a958f 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameClipsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameClipsDataSource.kt @@ -27,7 +27,8 @@ class GameClipsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -159,7 +160,7 @@ class GameClipsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -190,7 +191,7 @@ class GameClipsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameStreamsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameStreamsDataSource.kt index d2435e7a6..9097c4f7e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameStreamsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameStreamsDataSource.kt @@ -24,7 +24,8 @@ class GameStreamsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -149,7 +150,7 @@ class GameStreamsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -180,7 +181,7 @@ class GameStreamsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameVideosDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameVideosDataSource.kt index d44b5b390..a38e5cbb1 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameVideosDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GameVideosDataSource.kt @@ -32,7 +32,8 @@ class GameVideosDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -161,12 +162,12 @@ class GameVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -199,12 +200,12 @@ class GameVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GamesDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GamesDataSource.kt index ce1851597..1af29298e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GamesDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/GamesDataSource.kt @@ -19,7 +19,8 @@ class GamesDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -119,7 +120,7 @@ class GamesDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -148,7 +149,7 @@ class GamesDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchChannelsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchChannelsDataSource.kt index 17379f1c9..e908d7498 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchChannelsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchChannelsDataSource.kt @@ -18,7 +18,8 @@ class SearchChannelsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -114,7 +115,7 @@ class SearchChannelsDataSource( } } offset = data.edges.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchGamesDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchGamesDataSource.kt index 8501f0b0a..cdf3a3403 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchGamesDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchGamesDataSource.kt @@ -19,7 +19,8 @@ class SearchGamesDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -119,7 +120,7 @@ class SearchGamesDataSource( } } offset = data.edges.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchStreamsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchStreamsDataSource.kt index 374b10788..263f7ee02 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchStreamsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchStreamsDataSource.kt @@ -16,7 +16,8 @@ class SearchStreamsDataSource( private val gqlHeaders: Map, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -115,7 +116,7 @@ class SearchStreamsDataSource( } } offset = data.edges.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchVideosDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchVideosDataSource.kt index 9e39a7fc8..d0505a311 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchVideosDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/SearchVideosDataSource.kt @@ -16,7 +16,8 @@ class SearchVideosDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -89,11 +90,11 @@ class SearchVideosDataSource( name = tag.localizedName ) }, - animatedPreviewURL = it.animatedPreviewURL + animatedPreviewURL = it.animatedPreviewURL ) } offset = data.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/StreamsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/StreamsDataSource.kt index f9fc4c129..c569784e4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/StreamsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/StreamsDataSource.kt @@ -18,7 +18,8 @@ class StreamsDataSource( private val gqlApi: GraphQLRepository, private val apolloClient: ApolloClient, private val checkIntegrity: Boolean, - private val apiPref: List) : PagingSource() { + private val apiPref: List, +) : PagingSource() { private var api: String? = null private var offset: String? = null private var nextPage: Boolean = true @@ -138,7 +139,7 @@ class StreamsDataSource( } } offset = items.lastOrNull()?.cursor?.toString() - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } @@ -169,7 +170,7 @@ class StreamsDataSource( } } offset = items.lastOrNull()?.cursor - nextPage = data.pageInfo?.hasNextPage ?: true + nextPage = data.pageInfo?.hasNextPage != false return list } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/TagsDataSource.kt b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/TagsDataSource.kt index 1f1f514f5..e52c07b6e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/TagsDataSource.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/repository/datasource/TagsDataSource.kt @@ -9,7 +9,8 @@ class TagsDataSource( private val gqlHeaders: Map, private val getGameTags: Boolean, private val query: String, - private val api: GraphQLRepository) : PagingSource() { + private val api: GraphQLRepository, +) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerAdapter.kt index 0b95b25ac..ed0a21937 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerAdapter.kt @@ -8,7 +8,8 @@ import com.github.andreyasadchy.xtra.ui.videos.channel.ChannelVideosFragment class ChannelPagerAdapter( private val fragment: Fragment, - private val args: ChannelPagerFragmentArgs) : FragmentStateAdapter(fragment) { + private val args: ChannelPagerFragmentArgs, +) : FragmentStateAdapter(fragment) { override fun createFragment(position: Int): Fragment { return when (position) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerFragment.kt index 9d13ea5c7..febf77108 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerFragment.kt @@ -94,7 +94,11 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -107,12 +111,16 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In appBar.setExpanded(false, false) } if (viewModel.stream.value == null) { - watchLive.setOnClickListener { activity.startStream(Stream( - id = args.streamId, - channelId = args.channelId, - channelLogin = args.channelLogin, - channelName = args.channelName, - profileImageUrl = args.channelLogo)) + watchLive.setOnClickListener { + activity.startStream( + Stream( + id = args.streamId, + channelId = args.channelId, + channelLogin = args.channelLogin, + channelName = args.channelName, + profileImageUrl = args.channelLogo + ) + ) } } args.channelName.let { @@ -136,12 +144,17 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In if (it != null) { userLayout.visible() userImage.visible() - userImage.loadImage(this@ChannelPagerFragment, it, circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + this@ChannelPagerFragment, + it, + circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) } else { userImage.gone() } } - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) @@ -200,10 +213,31 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In } )) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), args.channelId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + args.channelId + ) + } .show() } else { - viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), args.channelId, args.channelLogin, args.channelName, if (args.updateLocal) { viewModel.stream.value?.channelLogo ?: viewModel.user.value?.channelLogo } else args.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) + viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + args.channelId, + args.channelLogin, + args.channelName, + if (args.updateLocal) { + viewModel.stream.value?.channelLogo ?: viewModel.user.value?.channelLogo + } else { + args.channelLogo + }, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) } } true @@ -407,7 +441,11 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In } override fun initialize() { - viewModel.loadStream(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.loadStream( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.stream.collectLatest { stream -> @@ -431,7 +469,15 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In } } } - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.channelId, args.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.channelId, + args.channelLogin + ) } private fun updateStreamLayout(stream: Stream?) { @@ -461,7 +507,11 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In if (it != null) { userLayout.visible() userImage.visible() - userImage.loadImage(this@ChannelPagerFragment, it, circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + this@ChannelPagerFragment, + it, + circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) requireArguments().putString(C.CHANNEL_PROFILEIMAGE, it) } else { userImage.gone() @@ -496,27 +546,27 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In if (!stream?.title.isNullOrBlank()) { streamLayout.visible() title.visible() - title.text = stream?.title?.trim() + title.text = stream.title?.trim() } else { title.gone() } if (!stream?.gameName.isNullOrBlank()) { streamLayout.visible() gameName.visible() - gameName.text = stream?.gameName + gameName.text = stream.gameName gameName.setOnClickListener { findNavController().navigate( if (requireContext().prefs().getBoolean(C.UI_GAMEPAGER, true)) { GamePagerFragmentDirections.actionGlobalGamePagerFragment( - gameId = stream?.gameId, - gameSlug = stream?.gameSlug, - gameName = stream?.gameName + gameId = stream.gameId, + gameSlug = stream.gameSlug, + gameName = stream.gameName ) } else { GameMediaFragmentDirections.actionGlobalGameMediaFragment( - gameId = stream?.gameId, - gameSlug = stream?.gameSlug, - gameName = stream?.gameName + gameId = stream.gameId, + gameSlug = stream.gameSlug, + gameName = stream.gameName ) } ) @@ -534,7 +584,7 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In if (requireContext().prefs().getBoolean(C.UI_UPTIME, true)) { if (stream?.startedAt != null) { TwitchApiHelper.getUptime(requireContext(), stream.startedAt).let { - if (it != null) { + if (it != null) { streamLayout.visible() uptime.visible() uptime.text = requireContext().getString(R.string.uptime, it) @@ -552,7 +602,11 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In if (!userImage.isVisible && user.channelLogo != null) { userLayout.visible() userImage.visible() - userImage.loadImage(this@ChannelPagerFragment, user.channelLogo, circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + this@ChannelPagerFragment, + user.channelLogo, + circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) requireArguments().putString(C.CHANNEL_PROFILEIMAGE, user.channelLogo) } if (user.bannerImageURL != null) { @@ -612,7 +666,11 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In } override fun onNetworkRestored() { - viewModel.retry(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.retry( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) } override fun onIntegrityDialogCallback(callback: String?) { @@ -621,13 +679,55 @@ class ChannelPagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, In repeatOnLifecycle(Lifecycle.State.STARTED) { when (callback) { "refresh" -> { - viewModel.retry(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.channelId, args.channelLogin) + viewModel.retry( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.channelId, + args.channelLogin + ) + } + "follow" -> viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + args.channelId, + args.channelLogin, + args.channelName, + args.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) + "unfollow" -> viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + args.channelId + ) + "enableNotifications" -> args.channelId?.let { + viewModel.enableNotifications( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + it, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) + } + "disableNotifications" -> args.channelId?.let { + viewModel.disableNotifications( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + it + ) } - "follow" -> viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), args.channelId, args.channelLogin, args.channelName, args.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) - "unfollow" -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), args.channelId) - "enableNotifications" -> args.channelId?.let { viewModel.enableNotifications(TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), it, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) } - "disableNotifications" -> args.channelId?.let { viewModel.disableNotifications(TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), it) } } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerViewModel.kt index 46cf5c3cc..16c544d2a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/channel/ChannelPagerViewModel.kt @@ -42,7 +42,8 @@ class ChannelPagerViewModel @Inject constructor( private val apolloClient: ApolloClient, private val helixApi: HelixApi, private val okHttpClient: OkHttpClient, - savedStateHandle: SavedStateHandle) : ViewModel() { + savedStateHandle: SavedStateHandle, +) : ViewModel() { val integrity = MutableStateFlow(null) @@ -78,7 +79,8 @@ class ChannelPagerViewModel @Inject constructor( viewModelScope.launch { try { _user.value = repository.loadUser(args.channelId, args.channelLogin, helixHeaders) - } catch (e: Exception) {} + } catch (e: Exception) { + } } } } @@ -277,7 +279,7 @@ class ChannelPagerViewModel @Inject constructor( fun updateLocalUser(filesDir: String, user: User) { if (!updatedLocalUser) { updatedLocalUser = true - user.channelId.takeIf { !it.isNullOrBlank()}?.let { userId -> + user.channelId.takeIf { !it.isNullOrBlank() }?.let { userId -> viewModelScope.launch { val downloadedLogo = user.channelLogo.takeIf { !it.isNullOrBlank() }?.let { File(filesDir, "profile_pics").mkdir() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatAdapter.kt index 9232aa9eb..5ff58a560 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatAdapter.kt @@ -61,7 +61,8 @@ class ChatAdapter( private val emoteQuality: String, private val animateGifs: Boolean, private val enableZeroWidth: Boolean, - private val channelId: String?) : RecyclerView.Adapter() { + private val channelId: String?, +) : RecyclerView.Adapter() { var messages: MutableList? = null set(value) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatFragment.kt index 554806c33..eac057ab4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatFragment.kt @@ -48,7 +48,11 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -61,7 +65,9 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia val channelId = args.getString(KEY_CHANNEL_ID) val isLive = args.getBoolean(KEY_IS_LIVE) val accountLogin = requireContext().tokenPrefs().getString(C.USERNAME, null) - val isLoggedIn = !accountLogin.isNullOrBlank() && (!TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank()) + val isLoggedIn = !accountLogin.isNullOrBlank() && + (!TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank()) val chatUrl = args.getString(KEY_CHAT_URL) val enableChat: Boolean if (isLive) { @@ -81,7 +87,8 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia val accountId = requireContext().tokenPrefs().getString(C.USER_ID, null) val useApiCommands = requireContext().prefs().getBoolean(C.DEBUG_API_COMMANDS, true) val useApiChatMessages = requireContext().prefs().getBoolean(C.DEBUG_API_CHAT_MESSAGES, false) - val checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + val checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) chatView.setCallback(object : ChatView.ChatViewCallback { override fun send(message: CharSequence, replyId: String?) { viewModel.send(message, replyId, helixHeaders, gqlHeaders, accountId, channelId, channelLogin, useApiCommands, useApiChatMessages, checkIntegrity) @@ -423,7 +430,10 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia } fun reloadEmotes() { - viewModel.reloadEmotes(requireArguments().getString(KEY_CHANNEL_ID), requireArguments().getString(KEY_CHANNEL_LOGIN)) + viewModel.reloadEmotes( + requireArguments().getString(KEY_CHANNEL_ID), + requireArguments().getString(KEY_CHANNEL_LOGIN) + ) } fun updatePosition(position: Long) { @@ -452,12 +462,14 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia } private fun onRaidClicked(raid: Raid) { - (requireActivity() as MainActivity).startStream(Stream( - channelId = raid.targetId, - channelLogin = raid.targetLogin, - channelName = raid.targetName, - profileImageUrl = raid.targetProfileImage, - )) + (requireActivity() as MainActivity).startStream( + Stream( + channelId = raid.targetId, + channelLogin = raid.targetLogin, + channelName = raid.targetName, + profileImageUrl = raid.targetProfileImage, + ) + ) } override fun onCreateMessageClickedChatAdapter(): MessageClickedChatAdapter { @@ -499,12 +511,14 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia } override fun onViewProfileClicked(id: String?, login: String?, name: String?, channelLogo: String?) { - findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = id, - channelLogin = login, - channelName = name, - channelLogo = channelLogo - )) + findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = id, + channelLogin = login, + channelName = name, + channelLogo = channelLogo + ) + ) (parentFragment as? BasePlayerFragment)?.minimize() } @@ -574,36 +588,42 @@ class ChatFragment : BaseNetworkFragment(), LifecycleListener, MessageClickedDia private const val KEY_START_TIME_EMPTY = "startTime_empty" private const val KEY_START_TIME = "startTime" - fun newInstance(channelId: String?, channelLogin: String?, channelName: String?, streamId: String?) = ChatFragment().apply { - arguments = Bundle().apply { - putBoolean(KEY_IS_LIVE, true) - putString(KEY_CHANNEL_ID, channelId) - putString(KEY_CHANNEL_LOGIN, channelLogin) - putString(KEY_CHANNEL_NAME, channelName) - putString(KEY_STREAM_ID, streamId) + fun newInstance(channelId: String?, channelLogin: String?, channelName: String?, streamId: String?): ChatFragment { + return ChatFragment().apply { + arguments = Bundle().apply { + putBoolean(KEY_IS_LIVE, true) + putString(KEY_CHANNEL_ID, channelId) + putString(KEY_CHANNEL_LOGIN, channelLogin) + putString(KEY_CHANNEL_NAME, channelName) + putString(KEY_STREAM_ID, streamId) + } } } - fun newInstance(channelId: String?, channelLogin: String?, videoId: String?, startTime: Int?) = ChatFragment().apply { - arguments = Bundle().apply { - putBoolean(KEY_IS_LIVE, false) - putString(KEY_CHANNEL_ID, channelId) - putString(KEY_CHANNEL_LOGIN, channelLogin) - putString(KEY_VIDEO_ID, videoId) - if (startTime != null) { - putBoolean(KEY_START_TIME_EMPTY, false) - putInt(KEY_START_TIME, startTime) - } else { - putBoolean(KEY_START_TIME_EMPTY, true) + fun newInstance(channelId: String?, channelLogin: String?, videoId: String?, startTime: Int?): ChatFragment { + return ChatFragment().apply { + arguments = Bundle().apply { + putBoolean(KEY_IS_LIVE, false) + putString(KEY_CHANNEL_ID, channelId) + putString(KEY_CHANNEL_LOGIN, channelLogin) + putString(KEY_VIDEO_ID, videoId) + if (startTime != null) { + putBoolean(KEY_START_TIME_EMPTY, false) + putInt(KEY_START_TIME, startTime) + } else { + putBoolean(KEY_START_TIME_EMPTY, true) + } } } } - fun newLocalInstance(channelId: String?, channelLogin: String?, chatUrl: String?) = ChatFragment().apply { - arguments = Bundle().apply { - putString(KEY_CHANNEL_ID, channelId) - putString(KEY_CHANNEL_LOGIN, channelLogin) - putString(KEY_CHAT_URL, chatUrl) + fun newLocalInstance(channelId: String?, channelLogin: String?, chatUrl: String?): ChatFragment { + return ChatFragment().apply { + arguments = Bundle().apply { + putString(KEY_CHANNEL_ID, channelId) + putString(KEY_CHANNEL_LOGIN, channelLogin) + putString(KEY_CHAT_URL, chatUrl) + } } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManager.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManager.kt index 7581af5b4..26a33e923 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManager.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManager.kt @@ -25,7 +25,8 @@ class ChatReplayManager @Inject constructor( private val onMessage: (ChatMessage) -> Unit, private val clearMessages: () -> Unit, private val getIntegrityToken: () -> Unit, - private val coroutineScope: CoroutineScope) { + private val coroutineScope: CoroutineScope, +) { private var cursor: String? = null private val list = mutableListOf() @@ -128,18 +129,20 @@ class ChatReplayManager @Inject constructor( if (!isActive) { break } - onMessage(ChatMessage( - id = message.id, - userId = message.userId, - userLogin = message.userLogin, - userName = message.userName, - message = message.message, - color = message.color, - emotes = message.emotes, - badges = message.badges, - bits = 0, - fullMsg = message.fullMsg - )) + onMessage( + ChatMessage( + id = message.id, + userId = message.userId, + userLogin = message.userLogin, + userName = message.userName, + message = message.message, + color = message.color, + emotes = message.emotes, + badges = message.badges, + bits = 0, + fullMsg = message.fullMsg + ) + ) if (list.size <= 25 && !cursor.isNullOrBlank() && !isLoading) { load() } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManagerLocal.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManagerLocal.kt index 1841ed012..8f9580d46 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManagerLocal.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatReplayManagerLocal.kt @@ -18,7 +18,8 @@ class ChatReplayManagerLocal @Inject constructor( private val getCurrentSpeed: () -> Float?, private val onMessage: (ChatMessage) -> Unit, private val clearMessages: () -> Unit, - private val coroutineScope: CoroutineScope) { + private val coroutineScope: CoroutineScope, +) { private val list = mutableListOf() private var isLoading = false @@ -68,23 +69,25 @@ class ChatReplayManagerLocal @Inject constructor( if (!isActive) { break } - onMessage(ChatMessage( - id = message.id, - userId = message.userId, - userLogin = message.userLogin, - userName = message.userName, - message = message.message, - color = message.color, - emotes = message.emotes, - badges = message.badges, - isAction = message.isAction, - isFirst = message.isFirst, - bits = message.bits, - systemMsg = message.systemMsg, - msgId = message.msgId, - reward = message.reward, - fullMsg = message.fullMsg - )) + onMessage( + ChatMessage( + id = message.id, + userId = message.userId, + userLogin = message.userLogin, + userName = message.userName, + message = message.message, + color = message.color, + emotes = message.emotes, + badges = message.badges, + isAction = message.isAction, + isFirst = message.isFirst, + bits = message.bits, + systemMsg = message.systemMsg, + msgId = message.msgId, + reward = message.reward, + fullMsg = message.fullMsg + ) + ) } else if (!isActive) break list.remove(message) } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatView.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatView.kt index 8b163965e..7cb714320 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatView.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatView.kt @@ -309,7 +309,11 @@ class ChatView : ConstraintLayout { raidLayout.visible() raidLayout.setOnClickListener { callback?.onRaidClicked(raid) } raidImage.visible() - raidImage.loadImage(fragment, raid.targetLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + raidImage.loadImage( + fragment, + raid.targetLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) raidText.visible() raidClose.visible() raidClose.setOnClickListener { @@ -785,10 +789,11 @@ class ChatView : ConstraintLayout { } inner class AutoCompleteAdapter( - context: Context, - private val fragment: Fragment, - list: List, - private val emoteQuality: String) : ArrayAdapter(context, 0, list) { + context: Context, + private val fragment: Fragment, + list: List, + private val emoteQuality: String, + ) : ArrayAdapter(context, 0, list) { private var mFilter: ArrayFilter? = null @@ -848,12 +853,16 @@ class ChatView : ConstraintLayout { } viewHolder.containerView.apply { item as Emote - findViewById(R.id.image)?.loadImage(fragment, when (emoteQuality) { - "4" -> item.url4x ?: item.url3x ?: item.url2x ?: item.url1x - "3" -> item.url3x ?: item.url2x ?: item.url1x - "2" -> item.url2x ?: item.url1x - else -> item.url1x - }, diskCacheStrategy = DiskCacheStrategy.DATA) + findViewById(R.id.image)?.loadImage( + fragment, + when (emoteQuality) { + "4" -> item.url4x ?: item.url3x ?: item.url2x ?: item.url1x + "3" -> item.url3x ?: item.url2x ?: item.url1x + "2" -> item.url2x ?: item.url1x + else -> item.url1x + }, + diskCacheStrategy = DiskCacheStrategy.DATA + ) findViewById(R.id.name)?.text = item.name } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatViewModel.kt index 9732fc405..07bef77aa 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ChatViewModel.kt @@ -64,7 +64,8 @@ class ChatViewModel @Inject constructor( private val repository: ApiRepository, private val graphQLRepository: GraphQLRepository, private val playerRepository: PlayerRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val integrity = MutableStateFlow(null) @@ -156,7 +157,9 @@ class ChatViewModel @Inject constructor( if (applicationContext.prefs().getBoolean(C.CHAT_RECENT, true)) { loadRecentMessages(channelLogin) } - val isLoggedIn = !applicationContext.tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && (!TwitchApiHelper.getGQLHeaders(applicationContext, true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(applicationContext)[C.HEADER_TOKEN].isNullOrBlank()) + val isLoggedIn = !applicationContext.tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && + (!TwitchApiHelper.getGQLHeaders(applicationContext, true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(applicationContext)[C.HEADER_TOKEN].isNullOrBlank()) if (isLoggedIn) { loadUserEmotes(channelId) } @@ -200,7 +203,8 @@ class ChatViewModel @Inject constructor( val gqlHeaders = TwitchApiHelper.getGQLHeaders(applicationContext, true) val emoteQuality = applicationContext.prefs().getString(C.CHAT_IMAGE_QUALITY, "4") ?: "4" val animateGifs = applicationContext.prefs().getBoolean(C.ANIMATED_EMOTES, true) - val checkIntegrity = applicationContext.prefs().getBoolean(C.ENABLE_INTEGRITY, false) && applicationContext.prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + val checkIntegrity = applicationContext.prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + applicationContext.prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) savedGlobalBadges.also { saved -> if (!saved.isNullOrEmpty()) { _globalBadges.value = saved @@ -405,22 +409,28 @@ class ChatViewModel @Inject constructor( private fun loadUserEmotes(channelId: String?) { savedUserEmotes.also { saved -> if (!saved.isNullOrEmpty()) { - addEmotes(saved.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) }) - _userEmotes.value = saved.sortedByDescending { it.ownerId == channelId }.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) } + addEmotes( + saved.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } + ) + _userEmotes.value = saved.sortedByDescending { it.ownerId == channelId }.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } } else { val helixHeaders = TwitchApiHelper.getHelixHeaders(applicationContext) val gqlHeaders = TwitchApiHelper.getGQLHeaders(applicationContext, true) @@ -429,26 +439,33 @@ class ChatViewModel @Inject constructor( try { val accountId = applicationContext.tokenPrefs().getString(C.USER_ID, null) val animateGifs = applicationContext.prefs().getBoolean(C.ANIMATED_EMOTES, true) - val checkIntegrity = applicationContext.prefs().getBoolean(C.ENABLE_INTEGRITY, false) && applicationContext.prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + val checkIntegrity = applicationContext.prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + applicationContext.prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) repository.loadUserEmotes(helixHeaders, gqlHeaders, channelId, accountId, animateGifs, checkIntegrity).let { emotes -> if (emotes.isNotEmpty()) { val sorted = emotes.sortedByDescending { it.setId } - addEmotes(sorted.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) }) - _userEmotes.value = sorted.sortedByDescending { it.ownerId == channelId }.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) } + addEmotes( + sorted.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } + ) + _userEmotes.value = sorted.sortedByDescending { it.ownerId == channelId }.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } loadedUserEmotes = true } } @@ -1038,22 +1055,28 @@ class ChatViewModel @Inject constructor( if (emotes.isNotEmpty()) { val sorted = emotes.sortedByDescending { it.setId } savedUserEmotes = sorted - addEmotes(sorted.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) }) - _userEmotes.value = sorted.sortedByDescending { it.ownerId == channelId }.map { Emote( - name = it.name, - url1x = it.url1x, - url2x = it.url2x, - url3x = it.url3x, - url4x = it.url4x, - format = it.format - ) } + addEmotes( + sorted.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } + ) + _userEmotes.value = sorted.sortedByDescending { it.ownerId == channelId }.map { + Emote( + name = it.name, + url1x = it.url1x, + url2x = it.url2x, + url3x = it.url3x, + url4x = it.url4x, + format = it.format + ) + } } } catch (e: Exception) { Log.e(TAG, "Failed to load emote sets", e) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesAdapter.kt index 98fdf516d..fe5a2b5bb 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesAdapter.kt @@ -13,32 +13,40 @@ import com.github.andreyasadchy.xtra.model.chat.Emote import com.github.andreyasadchy.xtra.util.loadImage class EmotesAdapter( - private val fragment: Fragment, - private val clickListener: (Emote) -> Unit, - private val emoteQuality: String) : ListAdapter(object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Emote, newItem: Emote): Boolean { - return oldItem.name == newItem.name - } + private val fragment: Fragment, + private val clickListener: (Emote) -> Unit, + private val emoteQuality: String, +) : ListAdapter( + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Emote, newItem: Emote): Boolean { + return oldItem.name == newItem.name + } - override fun areContentsTheSame(oldItem: Emote, newItem: Emote): Boolean { - return true + override fun areContentsTheSame(oldItem: Emote, newItem: Emote): Boolean { + return true + } } - -}) { +) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.fragment_emotes_list_item, parent, false)) {} + return object : RecyclerView.ViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.fragment_emotes_list_item, parent, false) + ) {} } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val emote = getItem(position) (holder.itemView as ImageView).apply { - loadImage(fragment, when (emoteQuality) { - "4" -> emote.url4x ?: emote.url3x ?: emote.url2x ?: emote.url1x - "3" -> emote.url3x ?: emote.url2x ?: emote.url1x - "2" -> emote.url2x ?: emote.url1x - else -> emote.url1x - }, diskCacheStrategy = DiskCacheStrategy.DATA) + loadImage( + fragment, + when (emoteQuality) { + "4" -> emote.url4x ?: emote.url3x ?: emote.url2x ?: emote.url1x + "3" -> emote.url3x ?: emote.url2x ?: emote.url1x + "2" -> emote.url2x ?: emote.url1x + else -> emote.url1x + }, + diskCacheStrategy = DiskCacheStrategy.DATA + ) setOnClickListener { clickListener(emote) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesFragment.kt index 4b49194ab..2457720ce 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/EmotesFragment.kt @@ -182,6 +182,12 @@ class EmotesFragment : Fragment() { companion object { private const val KEY_POSITION = "position" - fun newInstance(position: Int) = EmotesFragment().apply { arguments = bundleOf(KEY_POSITION to position) } + fun newInstance(position: Int): EmotesFragment { + return EmotesFragment().apply { + arguments = bundleOf( + KEY_POSITION to position + ) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedDialog.kt index a26a3dad7..90b96e59b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedDialog.kt @@ -51,8 +51,17 @@ class ImageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callback const val GLOBAL_BTTV = "global_bttv" const val GLOBAL_FFZ = "global_ffz" - fun newInstance(url: String?, name: String?, source: String?, format: String?, isAnimated: Boolean?, emoteId: String?) = ImageClickedDialog().apply { - arguments = bundleOf(IMAGE_URL to url, IMAGE_NAME to name, IMAGE_SOURCE to source, IMAGE_FORMAT to format, IMAGE_ANIMATED to isAnimated, EMOTE_ID to emoteId) + fun newInstance(url: String?, name: String?, source: String?, format: String?, isAnimated: Boolean?, emoteId: String?): ImageClickedDialog { + return ImageClickedDialog().apply { + arguments = bundleOf( + IMAGE_URL to url, + IMAGE_NAME to name, + IMAGE_SOURCE to source, + IMAGE_FORMAT to format, + IMAGE_ANIMATED to isAnimated, + EMOTE_ID to emoteId + ) + } } } @@ -73,7 +82,11 @@ class ImageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callback viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -85,16 +98,18 @@ class ImageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callback val imageLibrary = requireContext().prefs().getString(C.CHAT_IMAGE_LIBRARY, "0") if (imageLibrary == "0" || (imageLibrary == "1" && !args.getString(IMAGE_FORMAT).equals("webp", true))) { requireContext().imageLoader.enqueue( - ImageRequest.Builder(requireContext()) - .data(args.getString(IMAGE_URL)) - .target(onSuccess = { - val result = it.asDrawable(resources) - if (result is Animatable && args.getBoolean(IMAGE_ANIMATED) && requireContext().prefs().getBoolean(C.ANIMATED_EMOTES, true)) { - (result as Animatable).start() + ImageRequest.Builder(requireContext()).apply { + data(args.getString(IMAGE_URL)) + target( + onSuccess = { + val result = it.asDrawable(resources) + if (result is Animatable && args.getBoolean(IMAGE_ANIMATED) && requireContext().prefs().getBoolean(C.ANIMATED_EMOTES, true)) { + (result as Animatable).start() + } + image.setImageDrawable(result) } - image.setImageDrawable(result) - }) - .build() + ) + }.build() ) } else { Glide.with(this@ImageClickedDialog) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedViewModel.kt index cdc9c25da..5e296711e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ImageClickedViewModel.kt @@ -10,7 +10,9 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class ImageClickedViewModel @Inject constructor(private val graphQLRepository: GraphQLRepository) : ViewModel() { +class ImageClickedViewModel @Inject constructor( + private val graphQLRepository: GraphQLRepository, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedChatAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedChatAdapter.kt index 3522cb241..8c29b31b5 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedChatAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedChatAdapter.kt @@ -78,15 +78,19 @@ class MessageClickedChatAdapter( var globalBadges: List?, var channelBadges: List?, var cheerEmotes: List?, - var selectedMessage: ChatMessage?) : RecyclerView.Adapter() { + var selectedMessage: ChatMessage?, +) : RecyclerView.Adapter() { val userId = selectedMessage?.userId val userLogin = selectedMessage?.userLogin - var messages: MutableList? = if (!userId.isNullOrBlank() || !userLogin.isNullOrBlank()) { - messages?.filter { - (!userId.isNullOrBlank() && it.userId == userId) || (!userLogin.isNullOrBlank() && it.userLogin == userLogin) - }?.toMutableList() - } else null ?: selectedMessage?.let { mutableListOf(it) } + var messages: MutableList? = + if (!userId.isNullOrBlank() || !userLogin.isNullOrBlank()) { + messages?.filter { + (!userId.isNullOrBlank() && it.userId == userId) || (!userLogin.isNullOrBlank() && it.userLogin == userLogin) + }?.toMutableList() + } else { + null + } ?: selectedMessage?.let { mutableListOf(it) } set(value) { val oldSize = field?.size ?: 0 if (oldSize > 0) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedDialog.kt index fd6d77a31..fd9203df6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedDialog.kt @@ -52,8 +52,13 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba private const val KEY_CHANNEL_ID = "channelId" private val savedUsers = mutableListOf>() - fun newInstance(messagingEnabled: Boolean, channelId: String?) = MessageClickedDialog().apply { - arguments = bundleOf(KEY_MESSAGING to messagingEnabled, KEY_CHANNEL_ID to channelId) + fun newInstance(messagingEnabled: Boolean, channelId: String?): MessageClickedDialog { + return MessageClickedDialog().apply { + arguments = bundleOf( + KEY_MESSAGING to messagingEnabled, + KEY_CHANNEL_ID to channelId + ) + } } } @@ -84,7 +89,11 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -133,10 +142,19 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba if (item != null) { updateUserLayout(item.first) item.first.channelName?.let { channelName -> - if (requireArguments().getBoolean(KEY_MESSAGING) && !selectedMessage.id.isNullOrBlank() && selectedMessage.userName.isNullOrBlank() && channelName.isNotBlank()) { + if (requireArguments().getBoolean(KEY_MESSAGING) && + !selectedMessage.id.isNullOrBlank() && + selectedMessage.userName.isNullOrBlank() && + channelName.isNotBlank() + ) { reply.visible() reply.setOnClickListener { - listener.onReplyClicked(selectedMessage.id, selectedMessage.userLogin, channelName, selectedMessage.message) + listener.onReplyClicked( + selectedMessage.id, + selectedMessage.userLogin, + channelName, + selectedMessage.message + ) dismiss() } } @@ -148,7 +166,8 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba targetId = if (selectedMessage.userId != targetId) targetId else null, helixHeaders = TwitchApiHelper.getHelixHeaders(requireContext()), gqlHeaders = TwitchApiHelper.getGQLHeaders(requireContext()), - checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) ) viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -160,10 +179,19 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba savedUsers.add(Pair(user, targetId)) updateUserLayout(user) adapter.selectedMessage?.let { selectedMessage -> - if (requireArguments().getBoolean(KEY_MESSAGING) && !selectedMessage.id.isNullOrBlank() && selectedMessage.userName.isNullOrBlank() && !user.channelName.isNullOrBlank()) { + if (requireArguments().getBoolean(KEY_MESSAGING) && + !selectedMessage.id.isNullOrBlank() && + selectedMessage.userName.isNullOrBlank() && + !user.channelName.isNullOrBlank() + ) { reply.visible() reply.setOnClickListener { - listener.onReplyClicked(selectedMessage.id, selectedMessage.userLogin, user.channelName, selectedMessage.message) + listener.onReplyClicked( + selectedMessage.id, + selectedMessage.userLogin, + user.channelName, + selectedMessage.message + ) dismiss() } } @@ -227,14 +255,18 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba if (user.bannerImageURL != null) { userLayout.visible() bannerImage.visible() - bannerImage.loadImage(requireParentFragment(), user.bannerImageURL) + bannerImage.loadImage(this@MessageClickedDialog, user.bannerImageURL) } else { bannerImage.gone() } if (user.channelLogo != null) { userLayout.visible() userImage.visible() - userImage.loadImage(requireParentFragment(), user.channelLogo, circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + this@MessageClickedDialog, + user.channelLogo, + circle = requireContext().prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) userImage.setOnClickListener { listener.onViewProfileClicked(user.channelId, user.channelLogin, user.channelName, user.channelLogo) dismiss() @@ -338,7 +370,8 @@ class MessageClickedDialog : BottomSheetDialogFragment(), IntegrityDialog.Callba targetId = if (userId != targetId) targetId else null, helixHeaders = TwitchApiHelper.getHelixHeaders(requireContext()), gqlHeaders = TwitchApiHelper.getGQLHeaders(requireContext()), - checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) ) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedViewModel.kt index 623d7dcff..b51950d33 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/MessageClickedViewModel.kt @@ -10,7 +10,9 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class MessageClickedViewModel @Inject constructor(private val repository: ApiRepository) : ViewModel() { +class MessageClickedViewModel @Inject constructor( + private val repository: ApiRepository, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedChatAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedChatAdapter.kt index 57919cac3..5f07881e2 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedChatAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedChatAdapter.kt @@ -77,10 +77,13 @@ class ReplyClickedChatAdapter( var globalBadges: List?, var channelBadges: List?, var cheerEmotes: List?, - var selectedMessage: ChatMessage?) : RecyclerView.Adapter() { + var selectedMessage: ChatMessage?, +) : RecyclerView.Adapter() { val threadParentId = selectedMessage?.reply?.threadParentId - var messages: MutableList? = messages?.filter { it.reply?.threadParentId == threadParentId || it.id == threadParentId }?.toMutableList() ?: selectedMessage?.let { mutableListOf(it) } + var messages: MutableList? = + messages?.filter { it.reply?.threadParentId == threadParentId || it.id == threadParentId }?.toMutableList() + ?: selectedMessage?.let { mutableListOf(it) } set(value) { val oldSize = field?.size ?: 0 if (oldSize > 0) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedDialog.kt index 0ff01fc8c..9340c5b30 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/chat/ReplyClickedDialog.kt @@ -36,8 +36,12 @@ class ReplyClickedDialog : BottomSheetDialogFragment() { companion object { private const val KEY_MESSAGING = "messaging" - fun newInstance(messagingEnabled: Boolean) = ReplyClickedDialog().apply { - arguments = bundleOf(KEY_MESSAGING to messagingEnabled) + fun newInstance(messagingEnabled: Boolean): ReplyClickedDialog { + return ReplyClickedDialog().apply { + arguments = bundleOf( + KEY_MESSAGING to messagingEnabled, + ) + } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/ClipsAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/ClipsAdapter.kt index 4a7043bee..0208c74b6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/ClipsAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/ClipsAdapter.kt @@ -29,7 +29,8 @@ import com.github.andreyasadchy.xtra.util.visible class ClipsAdapter( private val fragment: Fragment, private val showDownloadDialog: (Clip) -> Unit, - private val hideGame: Boolean = false) : PagingDataAdapter( + private val hideGame: Boolean = false, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Clip, newItem: Clip): Boolean = oldItem.id == newItem.id @@ -51,17 +52,22 @@ class ClipsAdapter( inner class PagingViewHolder( private val binding: FragmentVideosListItemBinding, private val fragment: Fragment, - private val hideGame: Boolean): RecyclerView.ViewHolder(binding.root) { + private val hideGame: Boolean, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Clip?) { with(binding) { if (item != null) { val context = fragment.requireContext() - val channelListener: (View) -> Unit = { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - )) } + val channelListener: (View) -> Unit = { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + ) + ) + } val gameListener: (View) -> Unit = { fragment.findNavController().navigate( if (context.prefs().getBoolean(C.UI_GAMEPAGER, true)) { @@ -83,7 +89,11 @@ class ClipsAdapter( (fragment.activity as MainActivity).startClip(item) } root.setOnLongClickListener { showDownloadDialog(item); true } - thumbnail.loadImage(fragment, item.thumbnail, diskCacheStrategy = DiskCacheStrategy.NONE) + thumbnail.loadImage( + fragment, + item.thumbnail, + diskCacheStrategy = DiskCacheStrategy.NONE + ) if (item.uploadDate != null) { val text = item.uploadDate.let { TwitchApiHelper.formatTimeString(context, it) } if (text != null) { @@ -107,14 +117,18 @@ class ClipsAdapter( } else { duration.gone() } - if (item.channelLogo != null) { + if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) userImage.setOnClickListener(channelListener) } else { userImage.gone() } - if (item.channelName != null) { + if (item.channelName != null) { username.visible() username.text = if (item.channelLogin != null && !item.channelLogin.equals(item.channelName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { @@ -129,13 +143,13 @@ class ClipsAdapter( } else { username.gone() } - if (item.title != null && item.title != "") { + if (item.title != null && item.title != "") { title.visible() title.text = item.title.trim() } else { title.gone() } - if (!hideGame && item.gameName != null) { + if (!hideGame && item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -146,7 +160,7 @@ class ClipsAdapter( PopupMenu(context, it).apply { inflate(R.menu.media_item) setOnMenuItemClickListener { - when(it.itemId) { + when (it.itemId) { R.id.download -> showDownloadDialog(item) R.id.share -> { context.startActivity(Intent.createChooser(Intent().apply { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ChannelClipsAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ChannelClipsAdapter.kt index f77008446..f20cfecae 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ChannelClipsAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ChannelClipsAdapter.kt @@ -27,7 +27,8 @@ import com.github.andreyasadchy.xtra.util.visible class ChannelClipsAdapter( private val fragment: Fragment, - private val showDownloadDialog: (Clip) -> Unit) : PagingDataAdapter( + private val showDownloadDialog: (Clip) -> Unit, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Clip, newItem: Clip): Boolean = oldItem.id == newItem.id @@ -48,7 +49,8 @@ class ChannelClipsAdapter( inner class PagingViewHolder( private val binding: FragmentVideosListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Clip?) { with(binding) { if (item != null) { @@ -74,7 +76,11 @@ class ChannelClipsAdapter( (fragment.activity as MainActivity).startClip(item) } root.setOnLongClickListener { showDownloadDialog(item); true } - thumbnail.loadImage(fragment, item.thumbnail, diskCacheStrategy = DiskCacheStrategy.NONE) + thumbnail.loadImage( + fragment, + item.thumbnail, + diskCacheStrategy = DiskCacheStrategy.NONE + ) if (item.uploadDate != null) { val text = item.uploadDate.let { TwitchApiHelper.formatTimeString(context, it) } if (text != null) { @@ -98,13 +104,13 @@ class ChannelClipsAdapter( } else { duration.gone() } - if (item.title != null && item.title != "") { + if (item.title != null && item.title != "") { title.visible() title.text = item.title.trim() } else { title.gone() } - if (item.gameName != null) { + if (item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -115,7 +121,7 @@ class ChannelClipsAdapter( PopupMenu(context, it).apply { inflate(R.menu.media_item) setOnMenuItemClickListener { - when(it.itemId) { + when (it.itemId) { R.id.download -> showDownloadDialog(item) R.id.share -> { context.startActivity(Intent.createChooser(Intent().apply { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsFragment.kt index c98925aee..4b801cf23 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsFragment.kt @@ -67,21 +67,26 @@ class ClipsFragment : BaseClipsFragment(), Scrollable, Sortable, VideosSortDialo viewLifecycleOwner.lifecycleScope.launch { if (viewModel.filter.value == null) { if (args.channelId != null || args.channelLogin != null) { - val sortValues = args.channelId?.let { viewModel.getSortChannel(it)?.takeIf { it.saveSort == true } } ?: viewModel.getSortChannel("default") + val sortValues = args.channelId?.let { + viewModel.getSortChannel(it)?.takeIf { it.saveSort == true } + } ?: viewModel.getSortChannel("default") viewModel.setFilter( period = sortValues?.clipPeriod, languageIndex = 0, saveSort = sortValues?.saveSort, ) } else { - val sortValues = args.gameId?.let { viewModel.getSortGame(it)?.takeIf { it.saveSort == true } } ?: viewModel.getSortGame("default") + val sortValues = args.gameId?.let { + viewModel.getSortGame(it)?.takeIf { it.saveSort == true } + } ?: viewModel.getSortGame("default") viewModel.setFilter( period = sortValues?.clipPeriod, languageIndex = sortValues?.clipLanguageIndex, saveSort = sortValues?.saveSort, ) } - viewModel.sortText.value = requireContext().getString(R.string.sort_and_period, + viewModel.sortText.value = requireContext().getString( + R.string.sort_and_period, requireContext().getString(R.string.view_count), requireContext().getString( when (viewModel.period) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsViewModel.kt index c34c33c16..6e111e278 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/clips/common/ClipsViewModel.kt @@ -39,7 +39,8 @@ class ClipsViewModel @Inject constructor( private val apolloClient: ApolloClient, private val sortChannelRepository: SortChannelRepository, private val sortGameRepository: SortGameRepository, - savedStateHandle: SavedStateHandle) : ViewModel() { + savedStateHandle: SavedStateHandle, +) : ViewModel() { private val args = GamePagerFragmentArgs.fromSavedStateHandle(savedStateHandle) val filter = MutableStateFlow(null) @@ -106,7 +107,9 @@ class ClipsViewModel @Inject constructor( val langList = mutableListOf() if (languageIndex != 0) { val langValues = applicationContext.resources.getStringArray(R.array.gqlUserLanguageValues).toList() - val item = Language.entries.find { lang -> lang.rawValue == langValues.elementAt(languageIndex) } + val item = Language.entries.find { lang -> + lang.rawValue == langValues.elementAt(languageIndex) + } if (item != null) { langList.add(item) } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/BaseNetworkFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/BaseNetworkFragment.kt index 49a41f98d..e69c820db 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/BaseNetworkFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/BaseNetworkFragment.kt @@ -36,8 +36,8 @@ abstract class BaseNetworkFragment : Fragment() { super.onCreate(savedInstanceState) if (enableNetworkCheck) { lastIsOnlineState = savedInstanceState?.getBoolean(LAST_KEY) ?: requireContext().isNetworkAvailable - shouldRestore = savedInstanceState?.getBoolean(RESTORE_KEY) ?: false - created = savedInstanceState?.getBoolean(CREATED_KEY) ?: false + shouldRestore = savedInstanceState?.getBoolean(RESTORE_KEY) == true + created = savedInstanceState?.getBoolean(CREATED_KEY) == true } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/PagedListFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/PagedListFragment.kt index 16673df04..1fb04fa43 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/PagedListFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/common/PagedListFragment.kt @@ -60,8 +60,12 @@ abstract class PagedListFragment : BaseNetworkFragment(), IntegrityDialog.Callba swipeRefresh.isRefreshing = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount != 0 } nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } @@ -84,7 +88,10 @@ abstract class PagedListFragment : BaseNetworkFragment(), IntegrityDialog.Callba } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadDialog.kt index 971811e40..c0612031a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadDialog.kt @@ -57,15 +57,35 @@ class DownloadDialog : DialogFragment(), IntegrityDialog.CallbackListener { private const val KEY_CLIP = "clip" fun newInstance(stream: Stream, keys: Array? = null, values: Array? = null): DownloadDialog { - return DownloadDialog().apply { arguments = bundleOf(KEY_STREAM to stream, KEY_URLS_KEYS to keys, KEY_URLS_VALUES to values) } + return DownloadDialog().apply { + arguments = bundleOf( + KEY_STREAM to stream, + KEY_URLS_KEYS to keys, + KEY_URLS_VALUES to values + ) + } } fun newInstance(video: Video, keys: Array? = null, values: Array? = null, totalDuration: Long? = null, currentPosition: Long? = null): DownloadDialog { - return DownloadDialog().apply { arguments = bundleOf(KEY_VIDEO to video, KEY_URLS_KEYS to keys, KEY_URLS_VALUES to values, KEY_VIDEO_TOTAL_DURATION to totalDuration, KEY_VIDEO_CURRENT_POSITION to currentPosition) } + return DownloadDialog().apply { + arguments = bundleOf( + KEY_VIDEO to video, + KEY_URLS_KEYS to keys, + KEY_URLS_VALUES to values, + KEY_VIDEO_TOTAL_DURATION to totalDuration, + KEY_VIDEO_CURRENT_POSITION to currentPosition + ) + } } fun newInstance(clip: Clip, keys: Array? = null, values: Array? = null): DownloadDialog { - return DownloadDialog().apply { arguments = bundleOf(KEY_CLIP to clip, KEY_URLS_KEYS to keys, KEY_URLS_VALUES to values) } + return DownloadDialog().apply { + arguments = bundleOf( + KEY_CLIP to clip, + KEY_URLS_KEYS to keys, + KEY_URLS_VALUES to values + ) + } } } @@ -93,7 +113,11 @@ class DownloadDialog : DialogFragment(), IntegrityDialog.CallbackListener { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadViewModel.kt index 7435115e3..da384773e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/DownloadViewModel.kt @@ -21,7 +21,8 @@ import javax.inject.Inject @HiltViewModel class DownloadViewModel @Inject constructor( @ApplicationContext private val applicationContext: Context, - private val playerRepository: PlayerRepository) : ViewModel() { + private val playerRepository: PlayerRepository, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/StreamDownloadWorker.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/StreamDownloadWorker.kt index 1c1926e2d..1b1d83ec2 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/StreamDownloadWorker.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/StreamDownloadWorker.kt @@ -72,7 +72,8 @@ import kotlin.math.max @HiltWorker class StreamDownloadWorker @AssistedInject constructor( @Assisted private val context: Context, - @Assisted parameters: WorkerParameters) : CoroutineWorker(context, parameters) { + @Assisted parameters: WorkerParameters, +) : CoroutineWorker(context, parameters) { @Inject lateinit var repository: ApiRepository @@ -894,11 +895,17 @@ class StreamDownloadWorker @AssistedInject constructor( setContentText(offlineVideo.channelName) setSmallIcon(android.R.drawable.stat_sys_download) setOngoing(true) - setContentIntent(PendingIntent.getActivity(context, offlineVideo.id, - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP - action = MainActivity.INTENT_OPEN_DOWNLOADS_TAB - }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)) + setContentIntent( + PendingIntent.getActivity( + context, + offlineVideo.id, + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + action = MainActivity.INTENT_OPEN_DOWNLOADS_TAB + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) addAction(android.R.drawable.ic_delete, ContextCompat.getString(context, R.string.stop), WorkManager.getInstance(context).createCancelPendingIntent(id)) }.build() return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/VideoDownloadWorker.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/VideoDownloadWorker.kt index 1e9f516a5..1b54489f9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/VideoDownloadWorker.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/download/VideoDownloadWorker.kt @@ -80,7 +80,8 @@ import javax.inject.Inject @HiltWorker class VideoDownloadWorker @AssistedInject constructor( @Assisted private val context: Context, - @Assisted parameters: WorkerParameters) : CoroutineWorker(context, parameters) { + @Assisted parameters: WorkerParameters, +) : CoroutineWorker(context, parameters) { @Inject lateinit var repository: ApiRepository @@ -436,12 +437,18 @@ class VideoDownloadWorker @AssistedInject constructor( setContentText(offlineVideo.name) setSmallIcon(android.R.drawable.stat_sys_download_done) setAutoCancel(true) - setContentIntent(PendingIntent.getActivity(context, -offlineVideo.id, - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP - action = MainActivity.INTENT_OPEN_DOWNLOADED_VIDEO - putExtra(MainActivity.KEY_VIDEO, offlineVideo) - }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)) + setContentIntent( + PendingIntent.getActivity( + context, + -offlineVideo.id, + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + action = MainActivity.INTENT_OPEN_DOWNLOADED_VIDEO + putExtra(MainActivity.KEY_VIDEO, offlineVideo) + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) }.build() notificationManager.notify(-offlineVideo.id, notification) } @@ -1104,11 +1111,17 @@ class VideoDownloadWorker @AssistedInject constructor( setSmallIcon(android.R.drawable.stat_sys_download) setProgress(offlineVideo.maxProgress, offlineVideo.progress, false) setOngoing(true) - setContentIntent(PendingIntent.getActivity(context, offlineVideo.id, - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP - action = MainActivity.INTENT_OPEN_DOWNLOADS_TAB - }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)) + setContentIntent( + PendingIntent.getActivity( + context, + offlineVideo.id, + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + action = MainActivity.INTENT_OPEN_DOWNLOADS_TAB + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) addAction(android.R.drawable.ic_delete, ContextCompat.getString(context, R.string.stop), WorkManager.getInstance(context).createCancelPendingIntent(id)) }.build() return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowMediaFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowMediaFragment.kt index 750b14612..1293ed86a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowMediaFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowMediaFragment.kt @@ -62,7 +62,8 @@ class FollowMediaFragment : Fragment(), Scrollable, FragmentHost { val activity = requireActivity() as MainActivity val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() toolbar.setupWithNavController(navController, appBarConfiguration) toolbar.menu.findItem(R.id.login).title = if (isLoggedIn) getString(R.string.log_out) else getString(R.string.log_in) toolbar.setOnMenuItemClickListener { menuItem -> diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerAdapter.kt index 7c1d927c8..751358834 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerAdapter.kt @@ -8,8 +8,9 @@ import com.github.andreyasadchy.xtra.ui.streams.followed.FollowedStreamsFragment import com.github.andreyasadchy.xtra.ui.videos.followed.FollowedVideosFragment class FollowPagerAdapter( - fragment: Fragment, - private val loggedIn: Boolean) : FragmentStateAdapter(fragment) { + fragment: Fragment, + private val loggedIn: Boolean, +) : FragmentStateAdapter(fragment) { override fun createFragment(position: Int): Fragment { return if (loggedIn) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerFragment.kt index ca08fc305..ac353c1d8 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/FollowPagerFragment.kt @@ -60,7 +60,8 @@ class FollowPagerFragment : Fragment(), Scrollable, FragmentHost { val activity = requireActivity() as MainActivity val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() toolbar.setupWithNavController(navController, appBarConfiguration) toolbar.menu.findItem(R.id.login).title = if (isLoggedIn) getString(R.string.log_out) else getString(R.string.log_in) toolbar.setOnMenuItemClickListener { menuItem -> @@ -120,20 +121,22 @@ class FollowPagerFragment : Fragment(), Scrollable, FragmentHost { }) if (firstLaunch) { val defaultItem = requireContext().prefs().getString(C.UI_FOLLOW_DEFAULT_PAGE, "0")?.toInt() - viewPager.setCurrentItem(if (loggedIn) { - when (defaultItem) { - 1 -> 2 - 2 -> 3 - 3 -> 0 - else -> 1 - } - } else { - when (defaultItem) { - 2 -> 2 - 3 -> 0 - else -> 1 - } - }, false) + viewPager.setCurrentItem( + if (loggedIn) { + when (defaultItem) { + 1 -> 2 + 2 -> 3 + 3 -> 0 + else -> 1 + } + } else { + when (defaultItem) { + 2 -> 2 + 3 -> 0 + else -> 1 + } + }, false + ) firstLaunch = false } viewPager.offscreenPageLimit = adapter.itemCount diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsAdapter.kt index 1a026c7c5..534c6736c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsAdapter.kt @@ -20,7 +20,8 @@ import com.github.andreyasadchy.xtra.util.prefs import com.github.andreyasadchy.xtra.util.visible class FollowedChannelsAdapter( - private val fragment: Fragment) : PagingDataAdapter( + private val fragment: Fragment, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.channelId == newItem.channelId @@ -39,25 +40,35 @@ class FollowedChannelsAdapter( inner class PagingViewHolder( private val binding: FragmentFollowedChannelsListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: User?) { with(binding) { if (item != null) { val context = fragment.requireContext() - root.setOnClickListener { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - updateLocal = item.followLocal - )) } - if (item.channelLogo != null) { + root.setOnClickListener { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + updateLocal = item.followLocal + ) + ) + } + if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), diskCacheStrategy = DiskCacheStrategy.NONE) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), + diskCacheStrategy = DiskCacheStrategy.NONE + ) } else { userImage.gone() } - if (item.channelName != null) { + if (item.channelName != null) { username.visible() username.text = if (item.channelLogin != null && !item.channelLogin.equals(item.channelName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsFragment.kt index cc8bc5e22..49c6dfef6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsFragment.kt @@ -55,7 +55,8 @@ class FollowedChannelsFragment : PagedListFragment(), Scrollable, Sortable, Foll sort = sortValues?.videoSort, order = sortValues?.videoType, ) - viewModel.sortText.value = requireContext().getString(R.string.sort_and_period, + viewModel.sortText.value = requireContext().getString( + R.string.sort_and_period, requireContext().getString( when (viewModel.sort) { FollowedChannelsSortDialog.SORT_FOLLOWED_AT -> R.string.time_followed diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsViewModel.kt index ee5c04a04..54e1fc85e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/channels/FollowedChannelsViewModel.kt @@ -37,7 +37,8 @@ class FollowedChannelsViewModel @Inject constructor( private val localFollowsChannel: LocalFollowChannelRepository, private val offlineRepository: OfflineRepository, private val bookmarksRepository: BookmarksRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val filter = MutableStateFlow(null) val sortText = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesAdapter.kt index d9d72ae99..d4da43b12 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesAdapter.kt @@ -29,7 +29,8 @@ import com.github.andreyasadchy.xtra.util.prefs import com.github.andreyasadchy.xtra.util.visible class FollowedGamesAdapter( - private val fragment: Fragment) : PagingDataAdapter( + private val fragment: Fragment, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem.gameId == newItem.gameId @@ -49,7 +50,8 @@ class FollowedGamesAdapter( inner class PagingViewHolder( private val binding: FragmentFollowedGamesListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Game?) { with(binding) { if (item != null) { @@ -73,13 +75,17 @@ class FollowedGamesAdapter( } ) } - if (item.boxArt != null) { + if (item.boxArt != null) { gameImage.visible() - gameImage.loadImage(fragment, item.boxArt, diskCacheStrategy = DiskCacheStrategy.NONE) + gameImage.loadImage( + fragment, + item.boxArt, + diskCacheStrategy = DiskCacheStrategy.NONE + ) } else { gameImage.gone() } - if (item.gameName != null) { + if (item.gameName != null) { gameName.visible() gameName.text = item.gameName } else { @@ -101,7 +107,10 @@ class FollowedGamesAdapter( tagsLayout.removeAllViews() tagsLayout.visible() val tagsFlowLayout = Flow(context).apply { - layoutParams = ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + layoutParams = ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topToTop = tagsLayout.id bottomToBottom = tagsLayout.id startToStart = tagsLayout.id @@ -122,9 +131,11 @@ class FollowedGamesAdapter( } if (tag.id != null) { text.setOnClickListener { - fragment.findNavController().navigate(GamesFragmentDirections.actionGlobalGamesFragment( - tags = arrayOf(tag.id) - )) + fragment.findNavController().navigate( + GamesFragmentDirections.actionGlobalGamesFragment( + tags = arrayOf(tag.id) + ) + ) } } val padding = context.convertDpToPixels(5f) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesViewModel.kt index 9af05c83f..691d7ba6e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/follow/games/FollowedGamesViewModel.kt @@ -22,7 +22,8 @@ class FollowedGamesViewModel @Inject constructor( @ApplicationContext applicationContext: Context, private val graphQLRepository: GraphQLRepository, private val apolloClient: ApolloClient, - private val localFollowsGame: LocalFollowGameRepository) : ViewModel() { + private val localFollowsGame: LocalFollowGameRepository, +) : ViewModel() { val flow = Pager( PagingConfig(pageSize = 30, prefetchDistance = 10, initialLoadSize = 30) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GameMediaFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GameMediaFragment.kt index 4134ab3d6..7104fc11b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GameMediaFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GameMediaFragment.kt @@ -75,7 +75,11 @@ class GameMediaFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -84,7 +88,8 @@ class GameMediaFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ } with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) @@ -99,10 +104,24 @@ class GameMediaFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ requireContext().getAlertDialogBuilder() .setMessage(requireContext().getString(R.string.unfollow_channel, args.gameName)) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId + ) + } .show() } else { - viewModel.saveFollowGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameSlug, args.gameName) + viewModel.saveFollowGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameSlug, + args.gameName + ) } } true @@ -229,10 +248,21 @@ class GameMediaFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ override fun initialize() { val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 if (setting < 2) { - viewModel.isFollowingGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameName) + viewModel.isFollowingGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameName + ) } if (args.updateLocal) { - viewModel.updateLocalGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), args.gameId, args.gameName) + viewModel.updateLocalGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + args.gameId, + args.gameName + ) } } @@ -266,11 +296,28 @@ class GameMediaFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ "refresh" -> { val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 if (setting < 2) { - viewModel.isFollowingGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameName) + viewModel.isFollowingGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameName + ) } } - "follow" -> viewModel.saveFollowGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.gameId, args.gameSlug, args.gameName) - "unfollow" -> viewModel.deleteFollowGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.gameId) + "follow" -> viewModel.saveFollowGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.gameId, + args.gameSlug, + args.gameName + ) + "unfollow" -> viewModel.deleteFollowGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.gameId + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerFragment.kt index bbec263f4..9634c6b6e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerFragment.kt @@ -72,7 +72,11 @@ class GamePagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -81,7 +85,8 @@ class GamePagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ } with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) @@ -96,10 +101,24 @@ class GamePagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ requireContext().getAlertDialogBuilder() .setMessage(requireContext().getString(R.string.unfollow_channel, args.gameName)) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId + ) + } .show() } else { - viewModel.saveFollowGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameSlug, args.gameName) + viewModel.saveFollowGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameSlug, + args.gameName + ) } } true @@ -223,10 +242,21 @@ class GamePagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ override fun initialize() { val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 if (setting < 2) { - viewModel.isFollowingGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameName) + viewModel.isFollowingGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameName + ) } if (args.updateLocal) { - viewModel.updateLocalGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), args.gameId, args.gameName) + viewModel.updateLocalGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + args.gameId, + args.gameName + ) } } @@ -246,11 +276,28 @@ class GamePagerFragment : BaseNetworkFragment(), Scrollable, FragmentHost, Integ "refresh" -> { val setting = requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0 if (setting < 2) { - viewModel.isFollowingGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, args.gameId, args.gameName) + viewModel.isFollowingGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + args.gameId, + args.gameName + ) } } - "follow" -> viewModel.saveFollowGame(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.gameId, args.gameSlug, args.gameName) - "unfollow" -> viewModel.deleteFollowGame(TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, args.gameId) + "follow" -> viewModel.saveFollowGame( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.gameId, + args.gameSlug, + args.gameName + ) + "unfollow" -> viewModel.deleteFollowGame( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().prefs().getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + args.gameId + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerViewModel.kt index 9b8c4b567..5aad97c27 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamePagerViewModel.kt @@ -23,7 +23,8 @@ import javax.inject.Inject class GamePagerViewModel @Inject constructor( private val repository: ApiRepository, private val localFollowsGame: LocalFollowGameRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesAdapter.kt index 8bc751531..95f03ff61 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesAdapter.kt @@ -25,7 +25,8 @@ import com.github.andreyasadchy.xtra.util.prefs import com.github.andreyasadchy.xtra.util.visible class GamesAdapter( - private val fragment: Fragment) : PagingDataAdapter( + private val fragment: Fragment, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem.gameId == newItem.gameId @@ -45,7 +46,8 @@ class GamesAdapter( inner class PagingViewHolder( private val binding: FragmentGamesListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Game?) { with(binding) { if (item != null) { @@ -67,13 +69,13 @@ class GamesAdapter( } ) } - if (item.boxArt != null) { + if (item.boxArt != null) { gameImage.visible() gameImage.loadImage(fragment, item.boxArt) } else { gameImage.gone() } - if (item.gameName != null) { + if (item.gameName != null) { gameName.visible() gameName.text = item.gameName } else { @@ -95,7 +97,10 @@ class GamesAdapter( tagsLayout.removeAllViews() tagsLayout.visible() val tagsFlowLayout = Flow(context).apply { - layoutParams = ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + layoutParams = ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topToTop = tagsLayout.id bottomToBottom = tagsLayout.id startToStart = tagsLayout.id @@ -116,9 +121,11 @@ class GamesAdapter( } if (tag.id != null) { text.setOnClickListener { - fragment.findNavController().navigate(GamesFragmentDirections.actionGlobalGamesFragment( - tags = arrayOf(tag.id) - )) + fragment.findNavController().navigate( + GamesFragmentDirections.actionGlobalGamesFragment( + tags = arrayOf(tag.id) + ) + ) } } val padding = context.convertDpToPixels(5f) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesFragment.kt index d94051cef..905d5647a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesFragment.kt @@ -57,7 +57,8 @@ class GamesFragment : PagedListFragment(), Scrollable { super.onViewCreated(view, savedInstanceState) with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) toolbar.setupWithNavController(navController, appBarConfiguration) @@ -128,7 +129,11 @@ class GamesFragment : PagedListFragment(), Scrollable { initializeAdapter(binding.recyclerViewLayout, pagingAdapter, enableScrollTopButton = !args.tags.isNullOrEmpty()) with(binding) { sortBar.root.visible() - sortBar.root.setOnClickListener { findNavController().navigate(TagSearchFragmentDirections.actionGlobalTagSearchFragment(getGameTags = true)) } + sortBar.root.setOnClickListener { + findNavController().navigate( + TagSearchFragmentDirections.actionGlobalTagSearchFragment(getGameTags = true) + ) + } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesViewModel.kt index c692bca63..085ad28df 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/games/GamesViewModel.kt @@ -24,7 +24,8 @@ class GamesViewModel @Inject constructor( private val graphQLRepository: GraphQLRepository, private val helix: HelixApi, private val apolloClient: ApolloClient, - savedStateHandle: SavedStateHandle) : ViewModel() { + savedStateHandle: SavedStateHandle, +) : ViewModel() { private val args = GamesFragmentArgs.fromSavedStateHandle(savedStateHandle) val flow = Pager( diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/login/LoginActivity.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/login/LoginActivity.kt index 1adb23c02..aedd028b4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/login/LoginActivity.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/login/LoginActivity.kt @@ -133,8 +133,8 @@ class LoginActivity : AppCompatActivity() { "channel:manage:raids", // raids "channel:manage:vips", // channels/vips "channel:moderate", - "chat:edit", // TODO remove - "chat:read", // TODO remove + "chat:edit", // irc + "chat:read", // irc "moderator:manage:announcements", // chat/announcements "moderator:manage:banned_users", // moderation/bans "moderator:manage:chat_messages", // moderation/chat @@ -167,7 +167,10 @@ class LoginActivity : AppCompatActivity() { } .setNeutralButton(R.string.to_enter_url) { _, _ -> val editText = EditText(this@LoginActivity).apply { - layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) } this@LoginActivity.getAlertDialogBuilder() .setTitle(R.string.enter_url) @@ -376,14 +379,16 @@ class LoginActivity : AppCompatActivity() { when (api) { C.HELIX -> TwitchApiHelper.addTokenPrefixHelix(token) else -> TwitchApiHelper.addTokenPrefixGQL(token) - }) - if (!response?.clientId.isNullOrBlank() && response?.clientId == - when (api) { + } + ) + if (response.clientId.isNotBlank() && + response.clientId == when (api) { C.HELIX -> helixClientId else -> gqlClientId - }) { - response?.userId?.let { userId = it } - response?.login?.let { userLogin = it } + } + ) { + response.userId?.let { userId = it } + response.login?.let { userLogin = it } when (api) { C.HELIX -> helixToken = token C.GQL -> gqlToken = token diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/IntegrityDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/IntegrityDialog.kt index 9b5fdfa41..c13e53e3b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/IntegrityDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/IntegrityDialog.kt @@ -43,7 +43,7 @@ class IntegrityDialog : DialogFragment() { _binding = DialogIntegrityBinding.inflate(layoutInflater) val context = requireContext() val builder = context.getAlertDialogBuilder() - .setView(binding.root) + .setView(binding.root) CookieManager.getInstance().removeAllCookies(null) val token = TwitchApiHelper.getGQLHeaders(context, true)[C.HEADER_TOKEN]?.removePrefix("OAuth ") if (!token.isNullOrBlank()) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/LiveNotificationWorker.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/LiveNotificationWorker.kt index e0c436cba..a363e80e9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/LiveNotificationWorker.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/LiveNotificationWorker.kt @@ -26,7 +26,8 @@ import javax.inject.Inject @HiltWorker class LiveNotificationWorker @AssistedInject constructor( @Assisted private val context: Context, - @Assisted parameters: WorkerParameters) : CoroutineWorker(context, parameters) { + @Assisted parameters: WorkerParameters, +) : CoroutineWorker(context, parameters) { @Inject lateinit var shownNotifications: ShownNotificationsRepository @@ -80,12 +81,18 @@ class LiveNotificationWorker @AssistedInject constructor( setContentText(it.title) setSmallIcon(R.drawable.notification_icon) setAutoCancel(true) - setContentIntent(PendingIntent.getActivity(context, it.channelId.hashCode(), - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP - action = MainActivity.INTENT_LIVE_NOTIFICATION - putExtra(MainActivity.KEY_VIDEO, it) - }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)) + setContentIntent( + PendingIntent.getActivity( + context, + it.channelId.hashCode(), + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + action = MainActivity.INTENT_LIVE_NOTIFICATION + putExtra(MainActivity.KEY_VIDEO, it) + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) }.build() notificationManager.notify(it.channelId.hashCode(), notification) } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt index 376fe34d9..a2aba179d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainActivity.kt @@ -1,6 +1,5 @@ package com.github.andreyasadchy.xtra.ui.main -import android.app.Activity import android.app.ActivityOptions import android.app.PictureInPictureParams import android.app.admin.DevicePolicyManager @@ -239,7 +238,11 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + prefs.getBoolean(C.ENABLE_INTEGRITY, false) && + prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(supportFragmentManager, it) viewModel.integrity.value = "done" } @@ -268,12 +271,12 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { windowInsets } settingsResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { recreate() } } loginResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { restartActivity() } } @@ -301,10 +304,18 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { } if (online) { if (!TwitchApiHelper.checkedValidation && prefs.getBoolean(C.VALIDATE_TOKENS, true)) { - viewModel.validate(TwitchApiHelper.getHelixHeaders(this@MainActivity), TwitchApiHelper.getGQLHeaders(this@MainActivity, true), this@MainActivity.tokenPrefs().getString(C.USER_ID, null), this@MainActivity.tokenPrefs().getString(C.USERNAME, null), this@MainActivity) + viewModel.validate( + TwitchApiHelper.getHelixHeaders(this@MainActivity), + TwitchApiHelper.getGQLHeaders(this@MainActivity, true), + this@MainActivity.tokenPrefs().getString(C.USER_ID, null), + this@MainActivity.tokenPrefs().getString(C.USERNAME, null), + this@MainActivity + ) } - if (!TwitchApiHelper.checkedUpdates && prefs.getBoolean(C.UPDATE_CHECK_ENABLED, false) && - (prefs.getString(C.UPDATE_CHECK_FREQUENCY, "7")?.toIntOrNull() ?: 7) * 86400000 + tokenPrefs().getLong(C.UPDATE_LAST_CHECKED, 0) < System.currentTimeMillis()) { + if (!TwitchApiHelper.checkedUpdates && + prefs.getBoolean(C.UPDATE_CHECK_ENABLED, false) && + (prefs.getString(C.UPDATE_CHECK_FREQUENCY, "7")?.toIntOrNull() ?: 7) * 86400000 + tokenPrefs().getLong(C.UPDATE_LAST_CHECKED, 0) < System.currentTimeMillis() + ) { viewModel.checkUpdates( prefs.getString(C.UPDATE_URL, null) ?: "https://api.github.com/repos/crackededed/xtra/releases/tags/latest", tokenPrefs().getLong(C.UPDATE_LAST_CHECKED, 0) @@ -434,13 +445,22 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { private fun restartActivity() { finish() - startActivity(Intent(this, MainActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) }, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle()) + startActivity( + Intent(this, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) + }, + ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle() + ) } override fun onUserLeaveHint() { super.onUserLeaveHint() - if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT < Build.VERSION_CODES.S && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0" && playerFragment?.enterPictureInPicture() == true) { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && + Build.VERSION.SDK_INT < Build.VERSION_CODES.S && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0" && + playerFragment?.enterPictureInPicture() == true + ) { try { enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } catch (e: IllegalStateException) { @@ -458,7 +478,12 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { val id = url.substringAfter("twitch.tv/videos/").takeIf { it.isNotBlank() }?.let { it.substringBefore("?", it.substringBefore("/")) } val offset = url.substringAfter("?t=").takeIf { it.isNotBlank() }?.let { (TwitchApiHelper.getDuration(it)?.toDouble() ?: 0.0) * 1000.0 } if (!id.isNullOrBlank()) { - viewModel.loadVideo(id, TwitchApiHelper.getHelixHeaders(this), TwitchApiHelper.getGQLHeaders(this), prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.loadVideo( + id, + TwitchApiHelper.getHelixHeaders(this), + TwitchApiHelper.getGQLHeaders(this), + prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.video.collectLatest { video -> @@ -476,7 +501,12 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { url.contains("/clip/") -> { val id = url.substringAfter("/clip/").takeIf { it.isNotBlank() }?.let { it.substringBefore("?", it.substringBefore("/")) } if (!id.isNullOrBlank()) { - viewModel.loadClip(id, TwitchApiHelper.getHelixHeaders(this), TwitchApiHelper.getGQLHeaders(this), prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.loadClip( + id, + TwitchApiHelper.getHelixHeaders(this), + TwitchApiHelper.getGQLHeaders(this), + prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.clip.collectLatest { clip -> @@ -494,7 +524,12 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { url.contains("clips.twitch.tv/") -> { val id = url.substringAfter("clips.twitch.tv/").takeIf { it.isNotBlank() }?.let { it.substringBefore("?", it.substringBefore("/")) } if (!id.isNullOrBlank()) { - viewModel.loadClip(id, TwitchApiHelper.getHelixHeaders(this), TwitchApiHelper.getGQLHeaders(this), prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.loadClip( + id, + TwitchApiHelper.getHelixHeaders(this), + TwitchApiHelper.getGQLHeaders(this), + prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.clip.collectLatest { clip -> @@ -546,19 +581,26 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { else -> { val login = url.substringAfter("twitch.tv/").takeIf { it.isNotBlank() }?.let { it.substringBefore("?", it.substringBefore("/")) } if (!login.isNullOrBlank()) { - viewModel.loadUser(login, TwitchApiHelper.getHelixHeaders(this), TwitchApiHelper.getGQLHeaders(this), prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) + viewModel.loadUser( + login, + TwitchApiHelper.getHelixHeaders(this), + TwitchApiHelper.getGQLHeaders(this), + prefs.getBoolean(C.ENABLE_INTEGRITY, false) && prefs.getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.user.collectLatest { user -> if (user != null) { if (!user.channelId.isNullOrBlank() || !user.channelLogin.isNullOrBlank()) { playerFragment?.minimize() - navController.navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = user.channelId, - channelLogin = user.channelLogin, - channelName = user.channelName, - channelLogo = user.channelLogo, - )) + navController.navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = user.channelId, + channelLogin = user.channelLogin, + channelName = user.channelName, + channelLogo = user.channelLogo, + ) + ) } viewModel.user.value = null } @@ -619,7 +661,6 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { //Navigation listeners fun startStream(stream: Stream) { -// playerFragment?.play(stream) startPlayer(StreamPlayerFragment.newInstance(stream)) } @@ -652,21 +693,23 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { //Player methods private fun startPlayer(fragment: BasePlayerFragment) { -// if (playerFragment == null) { playerFragment = fragment supportFragmentManager.beginTransaction() - .replace(R.id.playerContainer, fragment).commit() + .replace(R.id.playerContainer, fragment).commit() viewModel.onPlayerStarted() - if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0") { + if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0" + ) { setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(true).build()) } } fun closePlayer() { supportFragmentManager.beginTransaction() - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .remove(supportFragmentManager.findFragmentById(R.id.playerContainer)!!) - .commit() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .remove(supportFragmentManager.findFragmentById(R.id.playerContainer)!!) + .commit() playerFragment = null viewModel.onPlayerClosed() if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -741,7 +784,8 @@ class MainActivity : AppCompatActivity(), SlidingLayout.Listener { private fun initNavigation() { navController = (supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment).navController navController.setGraph(navController.navInflater.inflate(R.navigation.nav_graph).also { - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(this, true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(this)[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(this, true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(this)[C.HEADER_TOKEN].isNullOrBlank() val setting = prefs.getString(C.UI_STARTONFOLLOWED, "1")?.toIntOrNull() ?: 1 if ((isLoggedIn && setting < 2) || (!isLoggedIn && setting == 0)) { if (prefs.getBoolean(C.UI_FOLLOWPAGER, true)) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt index d2758799e..fbcd9f182 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/main/MainViewModel.kt @@ -58,7 +58,8 @@ class MainViewModel @Inject constructor( private val offlineRepository: OfflineRepository, private val authRepository: AuthRepository, private val okHttpClient: OkHttpClient, - private val json: Json) : ViewModel() { + private val json: Json, +) : ViewModel() { val integrity = MutableStateFlow(null) @@ -184,25 +185,27 @@ class MainViewModel @Inject constructor( filePath } } - val videoId = offlineRepository.saveVideo(OfflineVideo( - name = title, - channelId = channelId, - channelLogin = channelLogin, - channelName = channelName, - channelLogo = downloadedLogo, - thumbnail = downloadedThumbnail, - gameId = gameId, - gameSlug = gameSlug, - gameName = gameName, - uploadDate = startedAt?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, - downloadDate = System.currentTimeMillis(), - downloadPath = path, - status = OfflineVideo.STATUS_BLOCKED, - quality = if (!quality.contains("Audio", true)) quality else "audio", - downloadChat = downloadChat, - downloadChatEmotes = downloadChatEmotes, - live = true - )).toInt() + val videoId = offlineRepository.saveVideo( + OfflineVideo( + name = title, + channelId = channelId, + channelLogin = channelLogin, + channelName = channelName, + channelLogo = downloadedLogo, + thumbnail = downloadedThumbnail, + gameId = gameId, + gameSlug = gameSlug, + gameName = gameName, + uploadDate = startedAt?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, + downloadDate = System.currentTimeMillis(), + downloadPath = path, + status = OfflineVideo.STATUS_BLOCKED, + quality = if (!quality.contains("Audio", true)) quality else "audio", + downloadChat = downloadChat, + downloadChatEmotes = downloadChatEmotes, + live = true + ) + ).toInt() WorkManager.getInstance(applicationContext).enqueueUniqueWork( channelLogin, ExistingWorkPolicy.REPLACE, @@ -263,30 +266,32 @@ class MainViewModel @Inject constructor( filePath } } - val videoId = offlineRepository.saveVideo(OfflineVideo( - sourceUrl = url, - name = title, - channelId = channelId, - channelLogin = channelLogin, - channelName = channelName, - channelLogo = downloadedLogo, - thumbnail = downloadedThumbnail, - gameId = gameId, - gameSlug = gameSlug, - gameName = gameName, - uploadDate = uploadDate?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, - downloadDate = System.currentTimeMillis(), - downloadPath = path, - fromTime = from, - toTime = to, - status = OfflineVideo.STATUS_BLOCKED, - type = type, - videoId = id, - quality = if (!quality.contains("Audio", true)) quality else "audio", - downloadChat = downloadChat, - downloadChatEmotes = downloadChatEmotes, - playlistToFile = playlistToFile - )).toInt() + val videoId = offlineRepository.saveVideo( + OfflineVideo( + sourceUrl = url, + name = title, + channelId = channelId, + channelLogin = channelLogin, + channelName = channelName, + channelLogo = downloadedLogo, + thumbnail = downloadedThumbnail, + gameId = gameId, + gameSlug = gameSlug, + gameName = gameName, + uploadDate = uploadDate?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, + downloadDate = System.currentTimeMillis(), + downloadPath = path, + fromTime = from, + toTime = to, + status = OfflineVideo.STATUS_BLOCKED, + type = type, + videoId = id, + quality = if (!quality.contains("Audio", true)) quality else "audio", + downloadChat = downloadChat, + downloadChatEmotes = downloadChatEmotes, + playlistToFile = playlistToFile + ) + ).toInt() WorkManager.getInstance(applicationContext).enqueueUniqueWork( "download", ExistingWorkPolicy.APPEND_OR_REPLACE, @@ -347,29 +352,31 @@ class MainViewModel @Inject constructor( filePath } } - val videoId = offlineRepository.saveVideo(OfflineVideo( - sourceUrl = url, - sourceStartPosition = vodOffset?.toLong()?.times(1000L), - name = title, - channelId = channelId, - channelLogin = channelLogin, - channelName = channelName, - channelLogo = downloadedLogo, - thumbnail = downloadedThumbnail, - gameId = gameId, - gameSlug = gameSlug, - gameName = gameName, - duration = duration?.toLong()?.times(1000L), - uploadDate = uploadDate?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, - downloadDate = System.currentTimeMillis(), - downloadPath = path, - status = OfflineVideo.STATUS_BLOCKED, - videoId = videoId, - clipId = id, - quality = if (!quality.contains("Audio", true)) quality else "audio", - downloadChat = downloadChat, - downloadChatEmotes = downloadChatEmotes - )).toInt() + val videoId = offlineRepository.saveVideo( + OfflineVideo( + sourceUrl = url, + sourceStartPosition = vodOffset?.toLong()?.times(1000L), + name = title, + channelId = channelId, + channelLogin = channelLogin, + channelName = channelName, + channelLogo = downloadedLogo, + thumbnail = downloadedThumbnail, + gameId = gameId, + gameSlug = gameSlug, + gameName = gameName, + duration = duration?.toLong()?.times(1000L), + uploadDate = uploadDate?.let { TwitchApiHelper.parseIso8601DateUTC(it) }, + downloadDate = System.currentTimeMillis(), + downloadPath = path, + status = OfflineVideo.STATUS_BLOCKED, + videoId = videoId, + clipId = id, + quality = if (!quality.contains("Audio", true)) quality else "audio", + downloadChat = downloadChat, + downloadChatEmotes = downloadChatEmotes + ) + ).toInt() WorkManager.getInstance(applicationContext).enqueueUniqueWork( "download", ExistingWorkPolicy.APPEND_OR_REPLACE, @@ -457,14 +464,23 @@ class MainViewModel @Inject constructor( okHttpClient.newCall(Request.Builder().url(url).build()).execute().use { response -> if (response.isSuccessful) { val packageInstaller = applicationContext.packageManager.packageInstaller - val sessionId = packageInstaller.createSession(PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)) + val sessionId = packageInstaller.createSession( + PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + ) val session = packageInstaller.openSession(sessionId) session.openWrite("package", 0, -1).sink().buffer().use { sink -> sink.writeAll(response.body.source()) } - session.commit(PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, MainActivity::class.java).apply { - setAction(MainActivity.INTENT_INSTALL_UPDATE) - }, PendingIntent.FLAG_MUTABLE).intentSender) + session.commit( + PendingIntent.getActivity( + applicationContext, + 0, + Intent(applicationContext, MainActivity::class.java).apply { + setAction(MainActivity.INTENT_INSTALL_UPDATE) + }, + PendingIntent.FLAG_MUTABLE + ).intentSender + ) session.close() } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/BasePlayerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/BasePlayerFragment.kt index 61d8107c2..9001b4521 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/BasePlayerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/BasePlayerFragment.kt @@ -117,12 +117,21 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl prefs = activity.prefs() isPortrait = activity.isInPortraitOrientation activity.onBackPressedDispatcher.addCallback(this, backPressedCallback) - WindowCompat.getInsetsController(requireActivity().window, requireActivity().window.decorView).systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + WindowCompat.getInsetsController( + requireActivity().window, + requireActivity().window.decorView + ).systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } override fun onStart() { super.onStart() - controllerFuture = MediaController.Builder(requireActivity(), SessionToken(requireActivity(), ComponentName(requireActivity(), PlaybackService::class.java))).buildAsync() + controllerFuture = MediaController.Builder( + requireActivity(), + SessionToken( + requireActivity(), + ComponentName(requireActivity(), PlaybackService::class.java) + ) + ).buildAsync() controllerFuture.addListener({ val player = controllerFuture.get() requireView().findViewById(R.id.playerView)?.player = player @@ -189,7 +198,12 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl }) if (viewModel.background) { viewModel.background = false - player.sendCustomCommand(SessionCommand(PlaybackService.MOVE_FOREGROUND, Bundle.EMPTY), Bundle.EMPTY).let { result -> + player.sendCustomCommand( + SessionCommand( + PlaybackService.MOVE_FOREGROUND, + Bundle.EMPTY + ), Bundle.EMPTY + ).let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getString(PlaybackService.RESULT)?.let { @@ -200,7 +214,13 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl } } if (!viewModel.started) { - player.sendCustomCommand(SessionCommand(PlaybackService.CLEAR, Bundle.EMPTY), Bundle.EMPTY) + player.sendCustomCommand( + SessionCommand( + PlaybackService.CLEAR, + Bundle.EMPTY + ), + Bundle.EMPTY + ) if ((isInitialized || !enableNetworkCheck)) { startPlayer() } @@ -212,7 +232,11 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl super.onViewCreated(view, savedInstanceState) slidingLayout = view.findViewById(R.id.slidingLayout) slidingLayout.updateBackgroundColor(isPortrait) - chatLayout = if (this is ClipPlayerFragment) view.findViewById(R.id.clipChatContainer) else view.findViewById(R.id.chatFragmentContainer) + chatLayout = if (this is ClipPlayerFragment) { + view.findViewById(R.id.clipChatContainer) + } else { + view.findViewById(R.id.chatFragmentContainer) + } val ignoreCutouts = prefs.getBoolean(C.UI_DRAW_BEHIND_CUTOUTS, false) ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets -> val insets = if (!isPortrait && ignoreCutouts) { @@ -244,7 +268,11 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -328,7 +356,12 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl view.findViewById(R.id.playerMode)?.apply { visible() setOnClickListener { - player?.sendCustomCommand(SessionCommand(PlaybackService.SWITCH_AUDIO_MODE, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand( + PlaybackService.SWITCH_AUDIO_MODE, + Bundle.EMPTY + ), Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getString(PlaybackService.RESULT)?.let { @@ -345,7 +378,12 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl visible() setImageResource(if (prefs.getBoolean(C.PLAYER_AUDIO_COMPRESSOR, false)) R.drawable.baseline_audio_compressor_on_24dp else R.drawable.baseline_audio_compressor_off_24dp) setOnClickListener { - player?.sendCustomCommand(SessionCommand(PlaybackService.TOGGLE_DYNAMICS_PROCESSING, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand( + PlaybackService.TOGGLE_DYNAMICS_PROCESSING, + Bundle.EMPTY + ), Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val state = result.get().extras.getBoolean(PlaybackService.RESULT) @@ -357,7 +395,10 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl } } if (this is StreamPlayerFragment) { - if (!requireContext().tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && (!TwitchApiHelper.getGQLHeaders(activity, true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(activity)[C.HEADER_TOKEN].isNullOrBlank())) { + if (!requireContext().tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && + (!TwitchApiHelper.getGQLHeaders(activity, true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(activity)[C.HEADER_TOKEN].isNullOrBlank()) + ) { if (prefs.getBoolean(C.PLAYER_CHATBARTOGGLE, false) && !prefs.getBoolean(C.CHAT_DISABLE, false)) { view.findViewById(R.id.playerChatBarToggle)?.apply { visible() @@ -487,11 +528,23 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl override fun onSleepTimerChanged(durationMs: Long, hours: Int, minutes: Int, lockScreen: Boolean) { val context = requireContext() if (durationMs > 0L) { - context.toast(when { - hours == 0 -> getString(R.string.playback_will_stop, resources.getQuantityString(R.plurals.minutes, minutes, minutes)) - minutes == 0 -> getString(R.string.playback_will_stop, resources.getQuantityString(R.plurals.hours, hours, hours)) - else -> getString(R.string.playback_will_stop_hours_minutes, resources.getQuantityString(R.plurals.hours, hours, hours), resources.getQuantityString(R.plurals.minutes, minutes, minutes)) - }) + context.toast( + when { + hours == 0 -> getString( + R.string.playback_will_stop, + resources.getQuantityString(R.plurals.minutes, minutes, minutes) + ) + minutes == 0 -> getString( + R.string.playback_will_stop, + resources.getQuantityString(R.plurals.hours, hours, hours) + ) + else -> getString( + R.string.playback_will_stop_hours_minutes, + resources.getQuantityString(R.plurals.hours, hours, hours), + resources.getQuantityString(R.plurals.minutes, minutes, minutes) + ) + } + ) } else if (((activity as? MainActivity)?.getSleepTimerTimeLeft() ?: 0) > 0L) { context.toast(R.string.timer_canceled) } @@ -504,7 +557,12 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl override fun onChange(requestCode: Int, index: Int, text: CharSequence, tag: Int?) { when (requestCode) { REQUEST_CODE_QUALITY -> { - player?.sendCustomCommand(SessionCommand(PlaybackService.CHANGE_QUALITY, bundleOf(PlaybackService.INDEX to index)), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand( + PlaybackService.CHANGE_QUALITY, + bundleOf(PlaybackService.INDEX to index) + ), Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getString(PlaybackService.RESULT)?.let { @@ -570,7 +628,10 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl } fun showQualityDialog() { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_QUALITIES, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_QUALITIES, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val qualities = result.get().extras.getStringArray(PlaybackService.RESULT)?.toList() @@ -744,11 +805,17 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl } private fun showStatusBar() { - WindowCompat.getInsetsController(requireActivity().window, requireActivity().window.decorView).show(WindowInsetsCompat.Type.systemBars()) + WindowCompat.getInsetsController( + requireActivity().window, + requireActivity().window.decorView + ).show(WindowInsetsCompat.Type.systemBars()) } private fun hideStatusBar() { - WindowCompat.getInsetsController(requireActivity().window, requireActivity().window.decorView).hide(WindowInsetsCompat.Type.systemBars()) + WindowCompat.getInsetsController( + requireActivity().window, + requireActivity().window.decorView + ).hide(WindowInsetsCompat.Type.systemBars()) } fun setSubtitles(available: Boolean = null ?: subtitlesAvailable(), enabled: Boolean = null ?: subtitlesEnabled()) { @@ -815,10 +882,14 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl override fun onMovedToBackground() { viewModel.background = true - player?.sendCustomCommand(SessionCommand(PlaybackService.MOVE_BACKGROUND, bundleOf( - PlaybackService.PIP_MODE to viewModel.pipMode, - PlaybackService.DURATION to ((activity as? MainActivity)?.getSleepTimerTimeLeft() ?: 0) - )), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand( + PlaybackService.MOVE_BACKGROUND, bundleOf( + PlaybackService.PIP_MODE to viewModel.pipMode, + PlaybackService.DURATION to ((activity as? MainActivity)?.getSleepTimerTimeLeft() ?: 0) + ) + ), Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getString(PlaybackService.RESULT)?.let { @@ -841,12 +912,16 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl delay(1500L) try { player?.prepare() - } catch (e: Exception) {} + } catch (e: Exception) { + } } } fun setQualityText() { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_QUALITY_TEXT, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_QUALITY_TEXT, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val qualityText = result.get().extras.getString(PlaybackService.RESULT) @@ -861,7 +936,10 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl if (mode == PlaybackService.PLAYER_MODE_NORMAL) { playerView.controllerHideOnTouch = true playerView.controllerShowTimeoutMs = controllerShowTimeoutMs - if (requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0") { + if (requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "0" + ) { requireActivity().setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(true).build()) } } else { @@ -869,7 +947,9 @@ abstract class BasePlayerFragment : BaseNetworkFragment(), LifecycleListener, Sl playerView.controllerShowTimeoutMs = -1 playerView.showController() requireView().keepScreenOn = true - if (requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + ) { requireActivity().setPictureInPictureParams(PictureInPictureParams.Builder().setAutoEnterEnabled(false).build()) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlaybackService.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlaybackService.kt index 9cf05404e..7bc32fd7c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlaybackService.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlaybackService.kt @@ -90,119 +90,157 @@ class PlaybackService : MediaSessionService() { override fun onCreate() { super.onCreate() - val prefs = prefs() val player = ExoPlayer.Builder(this).apply { - setLoadControl(DefaultLoadControl.Builder().setBufferDurationsMs( - prefs.getString(C.PLAYER_BUFFER_MIN, "15000")?.toIntOrNull() ?: 15000, - prefs.getString(C.PLAYER_BUFFER_MAX, "50000")?.toIntOrNull() ?: 50000, - prefs.getString(C.PLAYER_BUFFER_PLAYBACK, "2000")?.toIntOrNull() ?: 2000, - prefs.getString(C.PLAYER_BUFFER_REBUFFER, "5000")?.toIntOrNull() ?: 5000 - ).build()) - setAudioAttributes(AudioAttributes.DEFAULT, prefs.getBoolean(C.PLAYER_AUDIO_FOCUS, false)) - setHandleAudioBecomingNoisy(prefs.getBoolean(C.PLAYER_HANDLE_AUDIO_BECOMING_NOISY, true)) - setSeekBackIncrementMs(prefs.getString(C.PLAYER_REWIND, "10000")?.toLongOrNull() ?: 10000) - setSeekForwardIncrementMs(prefs.getString(C.PLAYER_FORWARD, "10000")?.toLongOrNull() ?: 10000) - }.build().apply { - if (item != null) { - (urls.values.elementAtOrNull(qualityUrlIndex) ?: urls.values.elementAtOrNull(previousUrlIndex) ?: urls.values.firstOrNull())?.let { url -> - when (item) { - is Stream -> { - (item as Stream).let { item -> - HlsMediaSource.Factory(DefaultDataSource.Factory(this@PlaybackService, OkHttpDataSource.Factory(okHttpClient).apply { - headers?.let { - setDefaultRequestProperties(it) - } - })).apply { + setLoadControl( + DefaultLoadControl.Builder().apply { + setBufferDurationsMs( + prefs().getString(C.PLAYER_BUFFER_MIN, "15000")?.toIntOrNull() ?: 15000, + prefs().getString(C.PLAYER_BUFFER_MAX, "50000")?.toIntOrNull() ?: 50000, + prefs().getString(C.PLAYER_BUFFER_PLAYBACK, "2000")?.toIntOrNull() ?: 2000, + prefs().getString(C.PLAYER_BUFFER_REBUFFER, "5000")?.toIntOrNull() ?: 5000 + ) + }.build() + ) + setAudioAttributes( + AudioAttributes.DEFAULT, + prefs().getBoolean(C.PLAYER_AUDIO_FOCUS, false) + ) + setHandleAudioBecomingNoisy(prefs().getBoolean(C.PLAYER_HANDLE_AUDIO_BECOMING_NOISY, true)) + setSeekBackIncrementMs(prefs().getString(C.PLAYER_REWIND, "10000")?.toLongOrNull() ?: 10000) + setSeekForwardIncrementMs(prefs().getString(C.PLAYER_FORWARD, "10000")?.toLongOrNull() ?: 10000) + }.build() + if (item != null) { + (urls.values.elementAtOrNull(qualityUrlIndex) ?: urls.values.elementAtOrNull(previousUrlIndex) ?: urls.values.firstOrNull())?.let { url -> + when (item) { + is Stream -> { + (item as Stream).let { item -> + player.setMediaSource( + HlsMediaSource.Factory( + DefaultDataSource.Factory( + this, + OkHttpDataSource.Factory(okHttpClient).apply { + headers?.let { + setDefaultRequestProperties(it) + } + } + ) + ).apply { setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) setPlaylistTrackerFactory(DefaultHlsPlaylistTracker.FACTORY) setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(6)) - if (prefs.getBoolean(C.PLAYER_SUBTITLES, false) || prefs.getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { + if (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { setAllowChunklessPreparation(false) } - }.createMediaSource(MediaItem.Builder().apply { - setUri(url) - setMimeType(MimeTypes.APPLICATION_M3U8) - setLiveConfiguration(MediaItem.LiveConfiguration.Builder().apply { - prefs.getString(C.PLAYER_LIVE_MIN_SPEED, "")?.toFloatOrNull()?.let { setMinPlaybackSpeed(it) } - prefs.getString(C.PLAYER_LIVE_MAX_SPEED, "")?.toFloatOrNull()?.let { setMaxPlaybackSpeed(it) } - prefs.getString(C.PLAYER_LIVE_TARGET_OFFSET, "5000")?.toLongOrNull()?.let { setTargetOffsetMs(it) } - }.build()) - setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - }.build()).let { setMediaSource(it) } - volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - setPlaybackSpeed(1f) - prepare() - playWhenReady = true - setVideoQuality() - } + }.createMediaSource( + MediaItem.Builder().apply { + setUri(url) + setMimeType(MimeTypes.APPLICATION_M3U8) + setLiveConfiguration( + MediaItem.LiveConfiguration.Builder().apply { + prefs().getString(C.PLAYER_LIVE_MIN_SPEED, "")?.toFloatOrNull()?.let { setMinPlaybackSpeed(it) } + prefs().getString(C.PLAYER_LIVE_MAX_SPEED, "")?.toFloatOrNull()?.let { setMaxPlaybackSpeed(it) } + prefs().getString(C.PLAYER_LIVE_TARGET_OFFSET, "5000")?.toLongOrNull()?.let { setTargetOffsetMs(it) } + }.build() + ) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + ) + player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + player.setPlaybackSpeed(1f) + player.prepare() + player.playWhenReady = true + setVideoQuality() } - is Video -> { - (item as Video).let { item -> - HlsMediaSource.Factory(DefaultDataSource.Factory(this@PlaybackService, OkHttpDataSource.Factory(okHttpClient))).apply { + } + is Video -> { + (item as Video).let { item -> + player.setMediaSource( + HlsMediaSource.Factory( + DefaultDataSource.Factory( + this, + OkHttpDataSource.Factory(okHttpClient) + ) + ).apply { setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) - if (usingPlaylist && (prefs.getBoolean(C.PLAYER_SUBTITLES, false) || prefs.getBoolean(C.PLAYER_MENU_SUBTITLES, false))) { + if (usingPlaylist && (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false))) { setAllowChunklessPreparation(false) } - }.createMediaSource(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - .build() - ).let { setMediaSource(it) } - volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - prepare() - playWhenReady = true - seekTo(if (prefs().getBoolean(C.PLAYER_USE_VIDEOPOSITIONS, true)) getPosition() else 0) - setVideoQuality() - } + }.createMediaSource( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + ) + player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + player.prepare() + player.playWhenReady = true + player.seekTo(if (prefs().getBoolean(C.PLAYER_USE_VIDEOPOSITIONS, true)) getPosition() else 0) + setVideoQuality() } - is Clip -> { - (item as Clip).let { item -> - setMediaItem(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - .build()) - volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - prepare() - playWhenReady = true - setVideoQuality() - } + } + is Clip -> { + (item as Clip).let { item -> + player.setMediaItem( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + player.prepare() + player.playWhenReady = true + setVideoQuality() } - is OfflineVideo -> { - (item as OfflineVideo).let { item -> - setMediaItem(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.name) - .setArtist(item.channelName) - .build()) - .build()) - volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - prepare() - playWhenReady = true - seekTo(if (prefs().getBoolean(C.PLAYER_USE_VIDEOPOSITIONS, true)) getPosition() else 0) - setVideoQuality() - } + } + is OfflineVideo -> { + (item as OfflineVideo).let { item -> + player.setMediaItem( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.name) + setArtist(item.channelName) + }.build() + ) + }.build() + ) + player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + player.prepare() + player.playWhenReady = true + player.seekTo(if (prefs().getBoolean(C.PLAYER_USE_VIDEOPOSITIONS, true)) getPosition() else 0) + setVideoQuality() } } } } - reinitializeDynamicsProcessing(audioSessionId) - addListener(object : Player.Listener { + } + reinitializeDynamicsProcessing(player.audioSessionId) + player.addListener( + object : Player.Listener { override fun onTracksChanged(tracks: Tracks) { if (!tracks.isEmpty && usingPlaylist && (qualityIndex != -1 && qualityIndex != audioIndex)) { updateVideoQuality(qualityUrlIndex) @@ -233,12 +271,14 @@ class PlaybackService : MediaSessionService() { val matcher = codecPattern.matcher(tag) if (matcher.find()) { val codec = matcher.group(1)!! - codecs.add(when(codec) { - "av01" -> "AV1" - "hvc1" -> "H.265" - "avc1" -> "H.264" - else -> codec - }) + codecs.add( + when (codec) { + "av01" -> "AV1" + "hvc1" -> "H.265" + "avc1" -> "H.264" + else -> codec + } + ) } } } @@ -281,386 +321,440 @@ class PlaybackService : MediaSessionService() { override fun onPlayerError(error: PlaybackException) { if (background) { - prepare() + player.prepare() } } override fun onAudioSessionIdChanged(audioSessionId: Int) { reinitializeDynamicsProcessing(audioSessionId) } - }) - } - mediaSession = MediaSession.Builder(this, player) - .setSessionActivity(PendingIntent.getActivity(this, REQUEST_CODE_RESUME, - Intent(this, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP - action = MainActivity.INTENT_OPEN_PLAYER - }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + } + ) + mediaSession = MediaSession.Builder(this, player).apply { + setSessionActivity( + PendingIntent.getActivity( + this@PlaybackService, + REQUEST_CODE_RESUME, + Intent(this@PlaybackService, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + action = MainActivity.INTENT_OPEN_PLAYER + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) ) - .setCallback(object : MediaSession.Callback { - override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult { - val connectionResult = super.onConnect(session, controller) - val sessionCommands = connectionResult.availableSessionCommands.buildUpon() - .add(SessionCommand(START_STREAM, Bundle.EMPTY)) - .add(SessionCommand(START_VIDEO, Bundle.EMPTY)) - .add(SessionCommand(START_CLIP, Bundle.EMPTY)) - .add(SessionCommand(START_OFFLINE_VIDEO, Bundle.EMPTY)) - .add(SessionCommand(CHANGE_QUALITY, Bundle.EMPTY)) - .add(SessionCommand(START_AUDIO_ONLY, Bundle.EMPTY)) - .add(SessionCommand(SWITCH_AUDIO_MODE, Bundle.EMPTY)) - .add(SessionCommand(TOGGLE_DYNAMICS_PROCESSING, Bundle.EMPTY)) - .add(SessionCommand(TOGGLE_PROXY, Bundle.EMPTY)) - .add(SessionCommand(MOVE_BACKGROUND, Bundle.EMPTY)) - .add(SessionCommand(MOVE_FOREGROUND, Bundle.EMPTY)) - .add(SessionCommand(CLEAR, Bundle.EMPTY)) - .add(SessionCommand(GET_URLS, Bundle.EMPTY)) - .add(SessionCommand(GET_LAST_TAG, Bundle.EMPTY)) - .add(SessionCommand(GET_QUALITIES, Bundle.EMPTY)) - .add(SessionCommand(GET_QUALITY_TEXT, Bundle.EMPTY)) - .add(SessionCommand(GET_MEDIA_PLAYLIST, Bundle.EMPTY)) - .add(SessionCommand(GET_MULTIVARIANT_PLAYLIST, Bundle.EMPTY)) - .add(SessionCommand(GET_VIDEO_DOWNLOAD_INFO, Bundle.EMPTY)) - .add(SessionCommand(GET_ERROR_CODE, Bundle.EMPTY)) - .build() - return MediaSession.ConnectionResult.accept(sessionCommands, connectionResult.availablePlayerCommands) - } + setCallback( + object : MediaSession.Callback { + override fun onConnect(session: MediaSession, controller: MediaSession.ControllerInfo): MediaSession.ConnectionResult { + val connectionResult = super.onConnect(session, controller) + val sessionCommands = connectionResult.availableSessionCommands.buildUpon().apply { + add(SessionCommand(START_STREAM, Bundle.EMPTY)) + add(SessionCommand(START_VIDEO, Bundle.EMPTY)) + add(SessionCommand(START_CLIP, Bundle.EMPTY)) + add(SessionCommand(START_OFFLINE_VIDEO, Bundle.EMPTY)) + add(SessionCommand(CHANGE_QUALITY, Bundle.EMPTY)) + add(SessionCommand(START_AUDIO_ONLY, Bundle.EMPTY)) + add(SessionCommand(SWITCH_AUDIO_MODE, Bundle.EMPTY)) + add(SessionCommand(TOGGLE_DYNAMICS_PROCESSING, Bundle.EMPTY)) + add(SessionCommand(TOGGLE_PROXY, Bundle.EMPTY)) + add(SessionCommand(MOVE_BACKGROUND, Bundle.EMPTY)) + add(SessionCommand(MOVE_FOREGROUND, Bundle.EMPTY)) + add(SessionCommand(CLEAR, Bundle.EMPTY)) + add(SessionCommand(GET_URLS, Bundle.EMPTY)) + add(SessionCommand(GET_LAST_TAG, Bundle.EMPTY)) + add(SessionCommand(GET_QUALITIES, Bundle.EMPTY)) + add(SessionCommand(GET_QUALITY_TEXT, Bundle.EMPTY)) + add(SessionCommand(GET_MEDIA_PLAYLIST, Bundle.EMPTY)) + add(SessionCommand(GET_MULTIVARIANT_PLAYLIST, Bundle.EMPTY)) + add(SessionCommand(GET_VIDEO_DOWNLOAD_INFO, Bundle.EMPTY)) + add(SessionCommand(GET_ERROR_CODE, Bundle.EMPTY)) + }.build() + return MediaSession.ConnectionResult.accept(sessionCommands, connectionResult.availablePlayerCommands) + } - override fun onCustomCommand(session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle): ListenableFuture { - return when (customCommand.customAction) { - START_STREAM -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - customCommand.customExtras.getParcelable(ITEM, Stream::class.java) - } else { - @Suppress("DEPRECATION") - customCommand.customExtras.getParcelable(ITEM) - }?.let { item -> - usingProxy = false - stopProxy = false - usingPlaylist = true - usingAutoQuality = true - usingChatOnlyQuality = true - val uri = customCommand.customExtras.getString(URI) - val headers = args.getStringArray(HEADERS_KEYS)?.let { keys -> - args.getStringArray(HEADERS_VALUES)?.let { values -> - keys.zip(values).toMap(mutableMapOf()) + override fun onCustomCommand(session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle): ListenableFuture { + return when (customCommand.customAction) { + START_STREAM -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + customCommand.customExtras.getParcelable(ITEM, Stream::class.java) + } else { + @Suppress("DEPRECATION") + customCommand.customExtras.getParcelable(ITEM) + }?.let { item -> + usingProxy = false + stopProxy = false + usingPlaylist = true + usingAutoQuality = true + usingChatOnlyQuality = true + val uri = customCommand.customExtras.getString(URI) + val headers = args.getStringArray(HEADERS_KEYS)?.let { keys -> + args.getStringArray(HEADERS_VALUES)?.let { values -> + keys.zip(values).toMap(mutableMapOf()) + } } + val playlistAsData = customCommand.customExtras.getBoolean(PLAYLIST_AS_DATA) + Companion.item = item + Companion.headers = headers + (session.player as? ExoPlayer)?.setMediaSource( + HlsMediaSource.Factory( + DefaultDataSource.Factory( + this@PlaybackService, + OkHttpDataSource.Factory(okHttpClient).apply { + if (headers != null) { + setDefaultRequestProperties(headers) + } + } + ) + ).apply { + setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) + setPlaylistTrackerFactory(DefaultHlsPlaylistTracker.FACTORY) + setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(6)) + if (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { + setAllowChunklessPreparation(false) + } + }.createMediaSource( + MediaItem.Builder().apply { + if (playlistAsData) { + setUri("data:;base64,${uri}") + } else { + setUri(uri?.toUri()) + } + setMimeType(MimeTypes.APPLICATION_M3U8) + setLiveConfiguration(MediaItem.LiveConfiguration.Builder().apply { + prefs().getString(C.PLAYER_LIVE_MIN_SPEED, "")?.toFloatOrNull()?.let { setMinPlaybackSpeed(it) } + prefs().getString(C.PLAYER_LIVE_MAX_SPEED, "")?.toFloatOrNull()?.let { setMaxPlaybackSpeed(it) } + prefs().getString(C.PLAYER_LIVE_TARGET_OFFSET, "5000")?.toLongOrNull()?.let { setTargetOffsetMs(it) } + }.build()) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + ) + session.player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + session.player.setPlaybackSpeed(1f) + session.player.prepare() + session.player.playWhenReady = true } - val playlistAsData = customCommand.customExtras.getBoolean(PLAYLIST_AS_DATA) - Companion.item = item - Companion.headers = headers - HlsMediaSource.Factory(DefaultDataSource.Factory(this@PlaybackService, OkHttpDataSource.Factory(okHttpClient).apply { - if (headers != null) { - setDefaultRequestProperties(headers) - } - })).apply { - setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) - setPlaylistTrackerFactory(DefaultHlsPlaylistTracker.FACTORY) - setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(6)) - if (prefs.getBoolean(C.PLAYER_SUBTITLES, false) || prefs.getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { - setAllowChunklessPreparation(false) - } - }.createMediaSource(MediaItem.Builder().apply { - if (playlistAsData) { - setUri("data:;base64,${uri}") - } else { - setUri(uri?.toUri()) - } - setMimeType(MimeTypes.APPLICATION_M3U8) - setLiveConfiguration(MediaItem.LiveConfiguration.Builder().apply { - prefs.getString(C.PLAYER_LIVE_MIN_SPEED, "")?.toFloatOrNull()?.let { setMinPlaybackSpeed(it) } - prefs.getString(C.PLAYER_LIVE_MAX_SPEED, "")?.toFloatOrNull()?.let { setMaxPlaybackSpeed(it) } - prefs.getString(C.PLAYER_LIVE_TARGET_OFFSET, "5000")?.toLongOrNull()?.let { setTargetOffsetMs(it) } - }.build()) - setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - }.build()).let { (session.player as? ExoPlayer)?.setMediaSource(it) } - session.player.volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - session.player.setPlaybackSpeed(1f) - session.player.prepare() - session.player.playWhenReady = true + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - START_VIDEO -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - customCommand.customExtras.getParcelable(ITEM, Video::class.java) - } else { - @Suppress("DEPRECATION") - customCommand.customExtras.getParcelable(ITEM) - }?.let { item -> - val usingPlaylist = customCommand.customExtras.getBoolean(USING_PLAYLIST) - if (usingPlaylist) { - customCommand.customExtras.getString(URI)?.toUri() + START_VIDEO -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + customCommand.customExtras.getParcelable(ITEM, Video::class.java) } else { - item.animatedPreviewURL?.let { preview -> - val qualityMap = TwitchApiHelper.getVideoUrlMapFromPreview(preview, item.type) - val map = mutableMapOf>() - qualityMap.forEach { - if (it.key == "source") { - map[ContextCompat.getString(this@PlaybackService, R.string.source)] = Pair(it.key, it.value) - } else { - map[it.key] = Pair(it.key, it.value) + @Suppress("DEPRECATION") + customCommand.customExtras.getParcelable(ITEM) + }?.let { item -> + val usingPlaylist = customCommand.customExtras.getBoolean(USING_PLAYLIST) + if (usingPlaylist) { + customCommand.customExtras.getString(URI)?.toUri() + } else { + item.animatedPreviewURL?.let { preview -> + val qualityMap = TwitchApiHelper.getVideoUrlMapFromPreview(preview, item.type) + val map = mutableMapOf>() + qualityMap.forEach { + if (it.key == "source") { + map[ContextCompat.getString(this@PlaybackService, R.string.source)] = Pair(it.key, it.value) + } else { + map[it.key] = Pair(it.key, it.value) + } } - } - map.apply { - if (containsKey("audio_only")) { - remove("audio_only")?.let { url -> - put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), url) //move audio option to bottom + map.apply { + if (containsKey("audio_only")) { + remove("audio_only")?.let { url -> + put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), url) //move audio option to bottom + } + } else { + put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), Pair("audio_only", "")) } - } else { - put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), Pair("audio_only", "")) } + urls = map.values.associate { it.first to it.second } + qualities = LinkedList(map.keys).apply { + addFirst(ContextCompat.getString(this@PlaybackService, R.string.auto)) + } + qualityIndex = 1 + urls.values.first().toUri() } - urls = map.values.associate { it.first to it.second } - qualities = LinkedList(map.keys).apply { - addFirst(ContextCompat.getString(this@PlaybackService, R.string.auto)) - } - qualityIndex = 1 - urls.values.first().toUri() + }?.let { url -> + usingAutoQuality = true + Companion.usingPlaylist = usingPlaylist + Companion.item = item + (session.player as? ExoPlayer)?.setMediaSource( + HlsMediaSource.Factory( + DefaultDataSource.Factory( + this@PlaybackService, + OkHttpDataSource.Factory(okHttpClient) + ) + ).apply { + setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) + if (usingPlaylist && (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false))) { + setAllowChunklessPreparation(false) + } + }.createMediaSource( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + ) + session.player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + session.player.prepare() + session.player.playWhenReady = true + session.player.seekTo(customCommand.customExtras.getLong(PLAYBACK_POSITION)) } - }?.let { url -> - usingAutoQuality = true - Companion.usingPlaylist = usingPlaylist - Companion.item = item - HlsMediaSource.Factory(DefaultDataSource.Factory(this@PlaybackService, OkHttpDataSource.Factory(okHttpClient))).apply { - setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) - if (usingPlaylist && (prefs.getBoolean(C.PLAYER_SUBTITLES, false) || prefs.getBoolean(C.PLAYER_MENU_SUBTITLES, false))) { - setAllowChunklessPreparation(false) - } - }.createMediaSource(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - .build() - ).let { (session.player as? ExoPlayer)?.setMediaSource(it) } - session.player.volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - session.player.prepare() - session.player.playWhenReady = true - session.player.seekTo(customCommand.customExtras.getLong(PLAYBACK_POSITION)) } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - START_CLIP -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - customCommand.customExtras.getParcelable(ITEM, Clip::class.java) - } else { - @Suppress("DEPRECATION") - customCommand.customExtras.getParcelable(ITEM) - }?.let { item -> - customCommand.customExtras.getStringArray(URLS_KEYS)?.let { keys -> - customCommand.customExtras.getStringArray(URLS_VALUES)?.let { values -> - val map = mutableMapOf>() - keys.forEachIndexed { index, key -> - if (key == "source") { - map[ContextCompat.getString(this@PlaybackService, R.string.source)] = Pair(key, values[index]) - } else { - map[key] = Pair(key, values[index]) + START_CLIP -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + customCommand.customExtras.getParcelable(ITEM, Clip::class.java) + } else { + @Suppress("DEPRECATION") + customCommand.customExtras.getParcelable(ITEM) + }?.let { item -> + customCommand.customExtras.getStringArray(URLS_KEYS)?.let { keys -> + customCommand.customExtras.getStringArray(URLS_VALUES)?.let { values -> + val map = mutableMapOf>() + keys.forEachIndexed { index, key -> + if (key == "source") { + map[ContextCompat.getString(this@PlaybackService, R.string.source)] = Pair(key, values[index]) + } else { + map[key] = Pair(key, values[index]) + } } - } - map.apply { - if (containsKey("audio_only")) { - remove("audio_only")?.let { url -> - put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), url) //move audio option to bottom + map.apply { + if (containsKey("audio_only")) { + remove("audio_only")?.let { url -> + put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), url) //move audio option to bottom + } + } else { + put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), Pair("audio_only", "")) } - } else { - put(ContextCompat.getString(this@PlaybackService, R.string.audio_only), Pair("audio_only", "")) + } + Companion.item = item + urls = map.values.associate { it.first to it.second } + qualities = LinkedList(map.keys) + setQualityIndex() + (map.values.elementAtOrNull(qualityUrlIndex) ?: map.values.firstOrNull())?.second?.let { url -> + session.player.setMediaItem( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.title) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + session.player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + session.player.prepare() + session.player.playWhenReady = true } } + } + } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } + START_OFFLINE_VIDEO -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + customCommand.customExtras.getParcelable(ITEM, OfflineVideo::class.java) + } else { + @Suppress("DEPRECATION") + customCommand.customExtras.getParcelable(ITEM) + }?.let { item -> + item.url?.let { url -> Companion.item = item + val map = mapOf( + ContextCompat.getString(this@PlaybackService, R.string.source) to Pair("source", url), + ContextCompat.getString(this@PlaybackService, R.string.audio_only) to Pair("audio_only", "") + ) urls = map.values.associate { it.first to it.second } qualities = LinkedList(map.keys) - setQualityIndex() - (map.values.elementAtOrNull(qualityUrlIndex) ?: map.values.firstOrNull())?.second?.let { url -> - session.player.setMediaItem(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.title) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - .build()) - session.player.volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - session.player.prepare() - session.player.playWhenReady = true - } + session.player.setMediaItem( + MediaItem.Builder().apply { + setUri(url) + setMediaMetadata( + MediaMetadata.Builder().apply { + setTitle(item.name) + setArtist(item.channelName) + setArtworkUri(item.channelLogo?.toUri()) + }.build() + ) + }.build() + ) + session.player.volume = prefs().getInt(C.PLAYER_VOLUME, 100) / 100f + session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) + session.player.prepare() + session.player.playWhenReady = true + session.player.seekTo(customCommand.customExtras.getLong(PLAYBACK_POSITION)) } } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - START_OFFLINE_VIDEO -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - customCommand.customExtras.getParcelable(ITEM, OfflineVideo::class.java) - } else { - @Suppress("DEPRECATION") - customCommand.customExtras.getParcelable(ITEM) - }?.let { item -> - item.url?.let { url -> - Companion.item = item - val map = mapOf( - ContextCompat.getString(this@PlaybackService, R.string.source) to Pair("source", url), - ContextCompat.getString(this@PlaybackService, R.string.audio_only) to Pair("audio_only", "") - ) - urls = map.values.associate { it.first to it.second } - qualities = LinkedList(map.keys) - session.player.setMediaItem(MediaItem.Builder() - .setUri(url) - .setMediaMetadata(MediaMetadata.Builder() - .setTitle(item.name) - .setArtist(item.channelName) - .setArtworkUri(item.channelLogo?.toUri()) - .build()) - .build()) - session.player.volume = prefs.getInt(C.PLAYER_VOLUME, 100) / 100f - session.player.setPlaybackSpeed(prefs().getFloat(C.PLAYER_SPEED, 1f)) - session.player.prepare() - session.player.playWhenReady = true - session.player.seekTo(customCommand.customExtras.getLong(PLAYBACK_POSITION)) - } + CHANGE_QUALITY -> { + val index = customCommand.customExtras.getInt(INDEX) + changeQuality(index) + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to playerMode + ))) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - CHANGE_QUALITY -> { - val index = customCommand.customExtras.getInt(INDEX) - changeQuality(index) - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to playerMode))) - } - START_AUDIO_ONLY -> { - startAudioOnly() - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to playerMode))) - } - SWITCH_AUDIO_MODE -> { - if (playerMode != PLAYER_MODE_AUDIO_ONLY) { - changeQuality(audioIndex) - } else { - changeQuality(previousIndex) + START_AUDIO_ONLY -> { + startAudioOnly() + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to playerMode + ))) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to playerMode))) - } - TOGGLE_DYNAMICS_PROCESSING -> { - toggleDynamicsProcessing() - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to dynamicsProcessing?.enabled))) - } - TOGGLE_PROXY -> { - if (!stopProxy) { - toggleProxy(customCommand.customExtras.getBoolean(USING_PROXY)) + SWITCH_AUDIO_MODE -> { + if (playerMode != PLAYER_MODE_AUDIO_ONLY) { + changeQuality(audioIndex) + } else { + changeQuality(previousIndex) + } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to playerMode + ))) } - if (customCommand.customExtras.getBoolean(STOP_PROXY)) { - stopProxy = true + TOGGLE_DYNAMICS_PROCESSING -> { + toggleDynamicsProcessing() + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to dynamicsProcessing?.enabled + ))) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - MOVE_BACKGROUND -> { - val pipMode = customCommand.customExtras.getBoolean(PIP_MODE) - if (prefs.getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "2") { - savePosition() - session.player.stop() - } else { - if (playerMode == PLAYER_MODE_NORMAL) { - if (!pipMode && session.player.playbackState != Player.STATE_ENDED && session.player.playbackState != Player.STATE_IDLE && session.player.playWhenReady) { - startAudioOnly() - } else { - savePosition() - session.player.stop() - } + TOGGLE_PROXY -> { + if (!stopProxy) { + toggleProxy(customCommand.customExtras.getBoolean(USING_PROXY)) + } + if (customCommand.customExtras.getBoolean(STOP_PROXY)) { + stopProxy = true } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) } - background = true - val duration = customCommand.customExtras.getLong(DURATION) - if (duration > 0L) { - sleepTimer = Timer().apply { - schedule(duration) { - Handler(Looper.getMainLooper()).post { + MOVE_BACKGROUND -> { + val pipMode = customCommand.customExtras.getBoolean(PIP_MODE) + if (prefs().getString(C.PLAYER_BACKGROUND_PLAYBACK, "0") == "2") { + savePosition() + session.player.stop() + } else { + if (playerMode == PLAYER_MODE_NORMAL) { + if (!pipMode && + session.player.playbackState != Player.STATE_ENDED && + session.player.playbackState != Player.STATE_IDLE && + session.player.playWhenReady + ) { + startAudioOnly() + } else { savePosition() - session.player.pause() session.player.stop() - stopSelf() } } } + background = true + val duration = customCommand.customExtras.getLong(DURATION) + if (duration > 0L) { + sleepTimer = Timer().apply { + schedule(duration) { + Handler(Looper.getMainLooper()).post { + savePosition() + session.player.pause() + session.player.stop() + stopSelf() + } + } + } + } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to playerMode + ))) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to playerMode))) - } - MOVE_FOREGROUND -> { - background = false - sleepTimer?.let { - it.cancel() - sleepTimer = null - } - if (usingProxy) { - toggleProxy(false) - } - if (playerMode == PLAYER_MODE_NORMAL) { - session.player.prepare() - } else if (playerMode == PLAYER_MODE_AUDIO_ONLY) { - if (qualityIndex != audioIndex) { - setVideoQuality() + MOVE_FOREGROUND -> { + background = false + sleepTimer?.let { + it.cancel() + sleepTimer = null + } + if (usingProxy) { + toggleProxy(false) } + if (playerMode == PLAYER_MODE_NORMAL) { + session.player.prepare() + } else if (playerMode == PLAYER_MODE_AUDIO_ONLY) { + if (qualityIndex != audioIndex) { + setVideoQuality() + } + } + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to playerMode + ))) } - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to playerMode))) - } - CLEAR -> { - clear() - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) - } - GET_URLS -> Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - URLS_KEYS to urls.keys.toTypedArray(), - URLS_VALUES to urls.values.toTypedArray() - ))) - GET_LAST_TAG -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - RESULT to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.tags?.lastOrNull(), - USING_PROXY to usingProxy, - STOP_PROXY to stopProxy, - ITEM to (urls.values.elementAtOrNull(qualityUrlIndex) ?: urls.values.firstOrNull()) - ))) - } - GET_QUALITIES -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - RESULT to qualities.toTypedArray(), - INDEX to qualityIndex, - ))) - } - GET_QUALITY_TEXT -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf(RESULT to qualities.getOrNull(qualityIndex)))) - } - GET_MEDIA_PLAYLIST -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - RESULT to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.tags?.dropLastWhile { it == "ads=true" }?.joinToString("\n") - ))) - } - GET_MULTIVARIANT_PLAYLIST -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - RESULT to (session.player.currentManifest as? HlsManifest)?.multivariantPlaylist?.tags?.joinToString("\n") - ))) - } - GET_VIDEO_DOWNLOAD_INFO -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + CLEAR -> { + clear() + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } + GET_URLS -> Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( URLS_KEYS to urls.keys.toTypedArray(), - URLS_VALUES to urls.values.toTypedArray(), - TOTAL_DURATION to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.durationUs?.div(1000), - CURRENT_POSITION to session.player.currentPosition, - ))) - } - GET_ERROR_CODE -> { - Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( - RESULT to (session.player.playerError?.cause as? HttpDataSource.InvalidResponseCodeException)?.responseCode, + URLS_VALUES to urls.values.toTypedArray() ))) + GET_LAST_TAG -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.tags?.lastOrNull(), + USING_PROXY to usingProxy, + STOP_PROXY to stopProxy, + ITEM to (urls.values.elementAtOrNull(qualityUrlIndex) ?: urls.values.firstOrNull()) + ))) + } + GET_QUALITIES -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to qualities.toTypedArray(), + INDEX to qualityIndex, + ))) + } + GET_QUALITY_TEXT -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to qualities.getOrNull(qualityIndex) + ))) + } + GET_MEDIA_PLAYLIST -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.tags?.dropLastWhile { it == "ads=true" }?.joinToString("\n") + ))) + } + GET_MULTIVARIANT_PLAYLIST -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to (session.player.currentManifest as? HlsManifest)?.multivariantPlaylist?.tags?.joinToString("\n") + ))) + } + GET_VIDEO_DOWNLOAD_INFO -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + URLS_KEYS to urls.keys.toTypedArray(), + URLS_VALUES to urls.values.toTypedArray(), + TOTAL_DURATION to (session.player.currentManifest as? HlsManifest)?.mediaPlaylist?.durationUs?.div(1000), + CURRENT_POSITION to session.player.currentPosition, + ))) + } + GET_ERROR_CODE -> { + Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS, bundleOf( + RESULT to (session.player.playerError?.cause as? HttpDataSource.InvalidResponseCodeException)?.responseCode, + ))) + } + else -> super.onCustomCommand(session, controller, customCommand, args) } - else -> super.onCustomCommand(session, controller, customCommand, args) } } - }) - .build() + ) + }.build() } private fun changeQuality(index: Int) { @@ -808,7 +902,7 @@ class PlaybackService : MediaSessionService() { quality[0].filter(Char::isDigit).toIntOrNull()?.let { qualityRes -> val qualityFps = if (quality.size >= 2) quality[1].filter(Char::isDigit).toIntOrNull() ?: 30 else 30 (targetRes == qualityRes && targetFps >= qualityFps) || targetRes > qualityRes || qualities.indexOf(qualityString) == audioIndex - 1 - } ?: false + } == true } }).let { if (it != -1) it else null } } @@ -837,7 +931,9 @@ class PlaybackService : MediaSessionService() { dynamicsProcessing = DynamicsProcessing(0, audioSessionId, null).apply { for (channelIdx in 0 until channelCount) { for (bandIdx in 0 until getMbcByChannelIndex(channelIdx).bandCount) { - setMbcBandByChannelIndex(channelIdx, bandIdx, + setMbcBandByChannelIndex( + channelIdx, + bandIdx, getMbcBandByChannelIndex(channelIdx, bandIdx).apply { attackTime = 0f releaseTime = 0.25f @@ -846,7 +942,8 @@ class PlaybackService : MediaSessionService() { kneeWidth = 40f preGain = 0f postGain = 10f - }) + } + ) } } enabled = true @@ -861,39 +958,52 @@ class PlaybackService : MediaSessionService() { val proxyPort = prefs().getString(C.PROXY_PORT, null)?.toIntOrNull() val proxyUser = prefs().getString(C.PROXY_USER, null) val proxyPassword = prefs().getString(C.PROXY_PASSWORD, null) - HlsMediaSource.Factory(DefaultDataSource.Factory(this@PlaybackService, OkHttpDataSource.Factory( - if (enable && !proxyHost.isNullOrBlank() && proxyPort != null) { - okHttpClient.newBuilder().apply { - proxySelector(object : ProxySelector() { - override fun select(u: URI): List { - return if (Regex("video-weaver\\.\\w+\\.hls\\.ttvnw\\.net").matches(u.host)) { - listOf(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort)), Proxy.NO_PROXY) - } else { - listOf(Proxy.NO_PROXY) - } - } + (mediaSession?.player as? ExoPlayer)?.setMediaSource( + HlsMediaSource.Factory( + DefaultDataSource.Factory( + this, + OkHttpDataSource.Factory( + if (enable && !proxyHost.isNullOrBlank() && proxyPort != null) { + okHttpClient.newBuilder().apply { + proxySelector( + object : ProxySelector() { + override fun select(u: URI): List { + return if (Regex("video-weaver\\.\\w+\\.hls\\.ttvnw\\.net").matches(u.host)) { + listOf(Proxy(Proxy.Type.HTTP, InetSocketAddress(proxyHost, proxyPort)), Proxy.NO_PROXY) + } else { + listOf(Proxy.NO_PROXY) + } + } - override fun connectFailed(u: URI, sa: SocketAddress, e: IOException) {} - }) - if (!proxyUser.isNullOrBlank() && !proxyPassword.isNullOrBlank()) { - proxyAuthenticator { _, response -> - response.request.newBuilder().header( - "Proxy-Authorization", Credentials.basic(proxyUser, proxyPassword) - ).build() + override fun connectFailed(u: URI, sa: SocketAddress, e: IOException) {} + } + ) + if (!proxyUser.isNullOrBlank() && !proxyPassword.isNullOrBlank()) { + proxyAuthenticator { _, response -> + response.request.newBuilder().header( + "Proxy-Authorization", Credentials.basic(proxyUser, proxyPassword) + ).build() + } + } + }.build() + } else { + okHttpClient + } + ).apply { + headers?.let { + setDefaultRequestProperties(it) } } - }.build() - } else okHttpClient - ).apply { - headers?.let { setDefaultRequestProperties(it) } - })).apply { - setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) - setPlaylistTrackerFactory(DefaultHlsPlaylistTracker.FACTORY) - setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(6)) - if (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { - setAllowChunklessPreparation(false) - } - }.createMediaSource(item).let { (mediaSession?.player as? ExoPlayer)?.setMediaSource(it) } + ) + ).apply { + setPlaylistParserFactory(DefaultHlsPlaylistParserFactory()) + setPlaylistTrackerFactory(DefaultHlsPlaylistTracker.FACTORY) + setLoadErrorHandlingPolicy(DefaultLoadErrorHandlingPolicy(6)) + if (prefs().getBoolean(C.PLAYER_SUBTITLES, false) || prefs().getBoolean(C.PLAYER_MENU_SUBTITLES, false)) { + setAllowChunklessPreparation(false) + } + }.createMediaSource(item) + ) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialog.kt index 7b9032c36..36e3a6a76 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialog.kt @@ -44,7 +44,10 @@ class PlayerGamesDialog : BottomSheetDialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val context = requireContext() - val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + val params = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) val recycleView = GridRecyclerView(context).apply { id = R.id.recyclerView layoutParams = params diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialogAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialogAdapter.kt index a7e7f0884..610b9d3bf 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialogAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerGamesDialogAdapter.kt @@ -15,7 +15,8 @@ import com.github.andreyasadchy.xtra.util.loadImage import com.github.andreyasadchy.xtra.util.visible class PlayerGamesDialogAdapter( - private val fragment: Fragment) : ListAdapter( + private val fragment: Fragment, +) : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem.vodPosition == newItem.vodPosition @@ -34,21 +35,24 @@ class PlayerGamesDialogAdapter( inner class ViewHolder( private val binding: FragmentGamesListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Game?) { with(binding) { val context = fragment.requireContext() root.setOnClickListener { - item?.vodPosition?.let { position -> (fragment as? PlayerGamesDialog)?.listener?.seek(position.toLong()) } + item?.vodPosition?.let { position -> + (fragment as? PlayerGamesDialog)?.listener?.seek(position.toLong()) + } (fragment as? PlayerGamesDialog)?.dismiss() } - if (item?.boxArt != null) { + if (item?.boxArt != null) { gameImage.visible() gameImage.loadImage(fragment, item.boxArt) } else { gameImage.gone() } - if (item?.gameName != null) { + if (item?.gameName != null) { gameName.visible() gameName.text = item.gameName } else { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerSettingsDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerSettingsDialog.kt index 8760ed480..0b48264d4 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerSettingsDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerSettingsDialog.kt @@ -79,7 +79,9 @@ class PlayerSettingsDialog : BottomSheetDialogFragment() { } } if (!requireContext().prefs().getBoolean(C.CHAT_DISABLE, false)) { - val isLoggedIn = !requireContext().tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && (!TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank()) + val isLoggedIn = !requireContext().tokenPrefs().getString(C.USERNAME, null).isNullOrBlank() && + (!TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank()) if (isLoggedIn && requireContext().prefs().getBoolean(C.PLAYER_MENU_CHAT_BAR, true)) { menuChatBar.visible() if (requireContext().prefs().getBoolean(C.KEY_CHAT_BAR_VISIBLE, true)) { @@ -175,7 +177,10 @@ class PlayerSettingsDialog : BottomSheetDialogFragment() { } } (parentFragment as? BasePlayerFragment)?.setSubtitles() - if ((parentFragment is StreamPlayerFragment || parentFragment is VideoPlayerFragment) && !requireContext().prefs().getBoolean(C.CHAT_DISABLE, false) && requireContext().prefs().getBoolean(C.PLAYER_MENU_RELOAD_EMOTES, true)) { + if ((parentFragment is StreamPlayerFragment || parentFragment is VideoPlayerFragment) && + !requireContext().prefs().getBoolean(C.CHAT_DISABLE, false) && + requireContext().prefs().getBoolean(C.PLAYER_MENU_RELOAD_EMOTES, true) + ) { menuReloadEmotes.visible() menuReloadEmotes.setOnClickListener { (parentFragment as? BasePlayerFragment)?.reloadEmotes() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewModel.kt index 6c94101b7..0901a1645 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewModel.kt @@ -27,7 +27,8 @@ abstract class PlayerViewModel( private val localFollowsChannel: LocalFollowChannelRepository, private val shownNotificationsRepository: ShownNotificationsRepository, private val notificationUsersRepository: NotificationUsersRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListDialog.kt index 340c19dea..bd7cfc7bf 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListDialog.kt @@ -67,7 +67,11 @@ class PlayerViewerListDialog : BottomSheetDialogFragment(), IntegrityDialog.Call viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -208,17 +212,9 @@ class PlayerViewerListDialog : BottomSheetDialogFragment(), IntegrityDialog.Call return mData.size } - inner class ViewHolder internal constructor(itemView: View) : - RecyclerView.ViewHolder(itemView), - View.OnClickListener { - var textView: TextView = itemView.findViewById(R.id.userName) - override fun onClick(view: View) { + inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { - } - - init { - itemView.setOnClickListener(this) - } + val textView = itemView as TextView } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListViewModel.kt index 6ff7252f3..a24566cd9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/PlayerViewerListViewModel.kt @@ -11,7 +11,9 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class PlayerViewerListViewModel @Inject constructor(private val graphQLRepository: GraphQLRepository) : ViewModel() { +class PlayerViewerListViewModel @Inject constructor( + private val graphQLRepository: GraphQLRepository, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/SleepTimerDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/SleepTimerDialog.kt index d6a4072fd..ce2d4434d 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/SleepTimerDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/SleepTimerDialog.kt @@ -37,8 +37,8 @@ class SleepTimerDialog : DialogFragment() { _binding = DialogSleepTimerBinding.inflate(layoutInflater) val context = requireContext() val builder = context.getAlertDialogBuilder() - .setTitle(getString(R.string.sleep_timer)) - .setView(binding.root) + .setTitle(getString(R.string.sleep_timer)) + .setView(binding.root) with(binding) { hours.apply { minValue = 0 diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerFragment.kt index 9edfbbb68..54b3d62d7 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerFragment.kt @@ -108,18 +108,20 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { if (!item.videoId.isNullOrBlank()) { binding.watchVideo.visible() binding.watchVideo.setOnClickListener { - (requireActivity() as MainActivity).startVideo(Video( - id = item.videoId, - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - profileImageUrl = item.profileImageUrl, - animatedPreviewURL = item.videoAnimatedPreviewURL - ), (if (item.vodOffset != null) { - ((item.vodOffset?.toDouble() ?: 0.0) * 1000.0) + (player?.currentPosition ?: 0) - } else { - 0.0 - }), true) + (requireActivity() as MainActivity).startVideo( + Video( + id = item.videoId, + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + profileImageUrl = item.profileImageUrl, + animatedPreviewURL = item.videoAnimatedPreviewURL + ), (if (item.vodOffset != null) { + ((item.vodOffset?.toDouble() ?: 0.0) * 1000.0) + (player?.currentPosition ?: 0) + } else { + 0.0 + }), true + ) } } if (prefs.getBoolean(C.PLAYER_CHANNEL, true)) { @@ -135,12 +137,14 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { item.channelName } setOnClickListener { - findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo - )) + findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo + ) + ) slidingLayout.minimize() } } @@ -195,10 +199,27 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } )) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) + } .show() } else { - viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) + viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) } } } @@ -271,7 +292,15 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { override fun initialize() { super.initialize() - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) } override fun startPlayer() { @@ -291,11 +320,15 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { emptyMap() } } - player?.sendCustomCommand(SessionCommand(PlaybackService.START_CLIP, bundleOf( - PlaybackService.ITEM to clip, - PlaybackService.URLS_KEYS to urls.keys.toTypedArray(), - PlaybackService.URLS_VALUES to urls.values.toTypedArray() - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_CLIP, bundleOf( + PlaybackService.ITEM to clip, + PlaybackService.URLS_KEYS to urls.keys.toTypedArray(), + PlaybackService.URLS_VALUES to urls.values.toTypedArray() + ) + ), Bundle.EMPTY + ) viewModel.result.value = null } } @@ -303,11 +336,15 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } } else { val urls = TwitchApiHelper.getClipUrlMapFromPreview(clip.thumbnailUrl) - player?.sendCustomCommand(SessionCommand(PlaybackService.START_CLIP, bundleOf( - PlaybackService.ITEM to clip, - PlaybackService.URLS_KEYS to urls.keys.toTypedArray(), - PlaybackService.URLS_VALUES to urls.values.toTypedArray() - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_CLIP, bundleOf( + PlaybackService.ITEM to clip, + PlaybackService.URLS_KEYS to urls.keys.toTypedArray(), + PlaybackService.URLS_VALUES to urls.values.toTypedArray() + ) + ), Bundle.EMPTY + ) } } } @@ -318,7 +355,10 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { override fun showDownloadDialog() { if (viewModel.loaded.value) { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_URLS, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_URLS, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getStringArray(PlaybackService.URLS_KEYS)?.let { keys -> @@ -351,10 +391,33 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { when (callback) { "refresh" -> { viewModel.load(TwitchApiHelper.getGQLHeaders(requireContext()), item.id) - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) } - "follow" -> viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) - "unfollow" -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) + "follow" -> viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) + "unfollow" -> viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) } } } @@ -370,7 +433,11 @@ class ClipPlayerFragment : BasePlayerFragment(), HasDownloadDialog { private const val KEY_CLIP = "clip" fun newInstance(clip: Clip): ClipPlayerFragment { - return ClipPlayerFragment().apply { arguments = bundleOf(KEY_CLIP to clip) } + return ClipPlayerFragment().apply { + arguments = bundleOf( + KEY_CLIP to clip + ) + } } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerViewModel.kt index 00b3fc377..32d3cd6ed 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/clip/ClipPlayerViewModel.kt @@ -20,7 +20,8 @@ class ClipPlayerViewModel @Inject constructor( shownNotificationsRepository: ShownNotificationsRepository, notificationUsersRepository: NotificationUsersRepository, okHttpClient: OkHttpClient, - private val playerRepository: PlayerRepository) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { + private val playerRepository: PlayerRepository, +) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { val result = MutableStateFlow?>(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerFragment.kt index ca42e10e7..b8e897d39 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerFragment.kt @@ -84,12 +84,14 @@ class OfflinePlayerFragment : BasePlayerFragment() { item.channelName } setOnClickListener { - findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo - )) + findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo + ) + ) slidingLayout.minimize() } } @@ -141,19 +143,27 @@ class OfflinePlayerFragment : BasePlayerFragment() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.video.collectLatest { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_OFFLINE_VIDEO, bundleOf( - PlaybackService.ITEM to (it ?: item), - PlaybackService.PLAYBACK_POSITION to (it?.lastWatchPosition ?: 0L), - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_OFFLINE_VIDEO, bundleOf( + PlaybackService.ITEM to (it ?: item), + PlaybackService.PLAYBACK_POSITION to (it?.lastWatchPosition ?: 0L), + ) + ), Bundle.EMPTY + ) } } } viewModel.getVideo(item.id) } else { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_OFFLINE_VIDEO, bundleOf( - PlaybackService.ITEM to item, - PlaybackService.PLAYBACK_POSITION to 0L, - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_OFFLINE_VIDEO, bundleOf( + PlaybackService.ITEM to item, + PlaybackService.PLAYBACK_POSITION to 0L, + ) + ), Bundle.EMPTY + ) } } @@ -182,7 +192,11 @@ class OfflinePlayerFragment : BasePlayerFragment() { private const val KEY_VIDEO = "video" fun newInstance(video: OfflineVideo): OfflinePlayerFragment { - return OfflinePlayerFragment().apply { arguments = bundleOf(KEY_VIDEO to video) } + return OfflinePlayerFragment().apply { + arguments = bundleOf( + KEY_VIDEO to video + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerViewModel.kt index 5dcf3fcdb..007f397fe 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/offline/OfflinePlayerViewModel.kt @@ -21,7 +21,8 @@ class OfflinePlayerViewModel @Inject constructor( shownNotificationsRepository: ShownNotificationsRepository, notificationUsersRepository: NotificationUsersRepository, okHttpClient: OkHttpClient, - private val offlineRepository: OfflineRepository) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { + private val offlineRepository: OfflineRepository, +) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { val video = MutableSharedFlow() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerFragment.kt index 870fe4f37..c7a0b3f6e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerFragment.kt @@ -107,15 +107,22 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { viewModel.stream.collectLatest { if (it != null) { chatFragment?.updateStreamId(it.id) - if (prefs.getBoolean(C.CHAT_DISABLE, false) || !prefs.getBoolean(C.CHAT_PUBSUB_ENABLED, true) || requireView().findViewById(R.id.playerViewersText)?.text.isNullOrBlank()) { + if (prefs.getBoolean(C.CHAT_DISABLE, false) || + !prefs.getBoolean(C.CHAT_PUBSUB_ENABLED, true) || + requireView().findViewById(R.id.playerViewersText)?.text.isNullOrBlank() + ) { updateViewerCount(it.viewerCount) } - if (prefs.getBoolean(C.CHAT_DISABLE, false) || !prefs.getBoolean(C.CHAT_PUBSUB_ENABLED, true) || + if (prefs.getBoolean(C.CHAT_DISABLE, false) || + !prefs.getBoolean(C.CHAT_PUBSUB_ENABLED, true) || requireView().findViewById(R.id.playerTitle)?.text.isNullOrBlank() || - requireView().findViewById(R.id.playerCategory)?.text.isNullOrBlank()) { + requireView().findViewById(R.id.playerCategory)?.text.isNullOrBlank() + ) { updateStreamInfo(it.title, it.gameId, it.gameSlug, it.gameName) } - if (prefs.getBoolean(C.PLAYER_SHOW_UPTIME, true) && requireView().findViewById(R.id.playerUptime)?.isVisible == false) { + if (prefs.getBoolean(C.PLAYER_SHOW_UPTIME, true) && + requireView().findViewById(R.id.playerUptime)?.isVisible == false + ) { it.startedAt?.let { date -> TwitchApiHelper.parseIso8601DateUTC(date)?.let { startedAtMs -> updateUptime(startedAtMs) @@ -170,12 +177,14 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { item.channelName } setOnClickListener { - findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo - )) + findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo + ) + ) slidingLayout.minimize() } } @@ -208,10 +217,28 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } )) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) + } .show() } else { - viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false), item.startedAt) + viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false), + item.startedAt + ) } } } @@ -284,7 +311,15 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { override fun initialize() { super.initialize() - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) } override fun startPlayer() { @@ -297,10 +332,13 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { stream = item, loop = requireContext().prefs().getBoolean(C.CHAT_DISABLE, false) || !requireContext().prefs().getBoolean(C.CHAT_PUBSUB_ENABLED, true) || - (requireContext().prefs().getBoolean(C.CHAT_POINTS_COLLECT, true) && !requireContext().tokenPrefs().getString(C.USER_ID, null).isNullOrBlank() && !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank()), + (requireContext().prefs().getBoolean(C.CHAT_POINTS_COLLECT, true) && + !requireContext().tokenPrefs().getString(C.USER_ID, null).isNullOrBlank() && + !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank()), helixHeaders = TwitchApiHelper.getHelixHeaders(requireContext()), gqlHeaders = TwitchApiHelper.getGQLHeaders(requireContext()), - checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + checkIntegrity = requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) ) } } @@ -323,12 +361,16 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } } if (viewModel.useProxy && !proxyUrl.isNullOrBlank()) { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_STREAM, bundleOf( - PlaybackService.ITEM to stream, - PlaybackService.URI to proxyUrl.replace("\$channel", channelLogin), - PlaybackService.HEADERS_KEYS to headers?.keys?.toTypedArray(), - PlaybackService.HEADERS_VALUES to headers?.values?.toTypedArray() - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_STREAM, bundleOf( + PlaybackService.ITEM to stream, + PlaybackService.URI to proxyUrl.replace("\$channel", channelLogin), + PlaybackService.HEADERS_KEYS to headers?.keys?.toTypedArray(), + PlaybackService.HEADERS_VALUES to headers?.values?.toTypedArray() + ) + ), Bundle.EMPTY + ) player?.prepare() } else { if (viewModel.useProxy) { @@ -356,13 +398,17 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.result.collectLatest { if (it != null) { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_STREAM, bundleOf( - PlaybackService.ITEM to stream, - PlaybackService.URI to it, - PlaybackService.HEADERS_KEYS to headers?.keys?.toTypedArray(), - PlaybackService.HEADERS_VALUES to headers?.values?.toTypedArray(), - PlaybackService.PLAYLIST_AS_DATA to proxyMultivariantPlaylist - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_STREAM, bundleOf( + PlaybackService.ITEM to stream, + PlaybackService.URI to it, + PlaybackService.HEADERS_KEYS to headers?.keys?.toTypedArray(), + PlaybackService.HEADERS_VALUES to headers?.values?.toTypedArray(), + PlaybackService.PLAYLIST_AS_DATA to proxyMultivariantPlaylist + ) + ), Bundle.EMPTY + ) player?.prepare() viewModel.result.value = null } @@ -377,7 +423,10 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } fun checkAds() { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_LAST_TAG, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_LAST_TAG, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val tag = result.get().extras.getString(PlaybackService.RESULT) @@ -388,14 +437,29 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { viewModel.playingAds = tag == "ads=true" if (viewModel.playingAds) { if (usingProxy) { - player?.sendCustomCommand(SessionCommand(PlaybackService.TOGGLE_PROXY, bundleOf( - PlaybackService.USING_PROXY to false, - PlaybackService.STOP_PROXY to true - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.TOGGLE_PROXY, bundleOf( + PlaybackService.USING_PROXY to false, + PlaybackService.STOP_PROXY to true + ) + ), Bundle.EMPTY + ) } else { if (!oldValue) { - if (!stopProxy && !playlist.isNullOrBlank() && prefs.getBoolean(C.PROXY_MEDIA_PLAYLIST, true) && !prefs.getString(C.PROXY_HOST, null).isNullOrBlank() && prefs.getString(C.PROXY_PORT, null)?.toIntOrNull() != null) { - player?.sendCustomCommand(SessionCommand(PlaybackService.TOGGLE_PROXY, bundleOf(PlaybackService.USING_PROXY to true)), Bundle.EMPTY) + if (!stopProxy && + !playlist.isNullOrBlank() && + prefs.getBoolean(C.PROXY_MEDIA_PLAYLIST, true) && + !prefs.getString(C.PROXY_HOST, null).isNullOrBlank() && + prefs.getString(C.PROXY_PORT, null)?.toIntOrNull() != null + ) { + player?.sendCustomCommand( + SessionCommand( + PlaybackService.TOGGLE_PROXY, bundleOf( + PlaybackService.USING_PROXY to true + ) + ), Bundle.EMPTY + ) viewLifecycleOwner.lifecycleScope.launch { for (i in 0 until 10) { delay(10000) @@ -403,7 +467,13 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { break } } - player?.sendCustomCommand(SessionCommand(PlaybackService.TOGGLE_PROXY, bundleOf(PlaybackService.USING_PROXY to false)), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.TOGGLE_PROXY, bundleOf( + PlaybackService.USING_PROXY to false + ) + ), Bundle.EMPTY + ) } } else { if (prefs.getBoolean(C.PLAYER_HIDE_ADS, false)) { @@ -420,7 +490,10 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { override fun onError(error: PlaybackException) { Log.e(tag, "Player error", error) - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_ERROR_CODE, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_ERROR_CODE, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val responseCode = result.get().extras.getInt(PlaybackService.RESULT) @@ -436,7 +509,8 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { delay(1500L) try { restartPlayer() - } catch (e: Exception) {} + } catch (e: Exception) { + } } } else -> { @@ -445,7 +519,8 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { delay(1500L) try { restartPlayer() - } catch (e: Exception) {} + } catch (e: Exception) { + } } } } @@ -546,11 +621,22 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { } fun openViewerList() { - item.channelLogin?.let { login -> PlayerViewerListDialog.newInstance(login).show(childFragmentManager, "closeOnPip") } + item.channelLogin?.let { login -> + PlayerViewerListDialog.newInstance(login).show(childFragmentManager, "closeOnPip") + } } fun showPlaylistTags(mediaPlaylist: Boolean) { - player?.sendCustomCommand(SessionCommand(if (mediaPlaylist) PlaybackService.GET_MEDIA_PLAYLIST else PlaybackService.GET_MULTIVARIANT_PLAYLIST, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand( + if (mediaPlaylist) { + PlaybackService.GET_MEDIA_PLAYLIST + } else { + PlaybackService.GET_MULTIVARIANT_PLAYLIST + }, + Bundle.EMPTY + ), Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val tags = result.get().extras.getString(PlaybackService.RESULT) @@ -591,7 +677,10 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { override fun showDownloadDialog() { if (viewModel.loaded.value) { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_URLS, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_URLS, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getStringArray(PlaybackService.URLS_KEYS)?.let { keys -> @@ -637,10 +726,34 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { enableIntegrity = prefs.getBoolean(C.ENABLE_INTEGRITY, false) ) } - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) } - "follow" -> viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false), item.startedAt) - "unfollow" -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) + "follow" -> viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false), + item.startedAt + ) + "unfollow" -> viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) } } } @@ -656,7 +769,11 @@ class StreamPlayerFragment : BasePlayerFragment(), HasDownloadDialog { private const val KEY_STREAM = "stream" fun newInstance(stream: Stream): StreamPlayerFragment { - return StreamPlayerFragment().apply { arguments = bundleOf(KEY_STREAM to stream) } + return StreamPlayerFragment().apply { + arguments = bundleOf( + KEY_STREAM to stream + ) + } } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerViewModel.kt index 3716fe822..96e62f63a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/stream/StreamPlayerViewModel.kt @@ -25,7 +25,8 @@ class StreamPlayerViewModel @Inject constructor( shownNotificationsRepository: ShownNotificationsRepository, notificationUsersRepository: NotificationUsersRepository, okHttpClient: OkHttpClient, - private val playerRepository: PlayerRepository) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { + private val playerRepository: PlayerRepository, +) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { val result = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerFragment.kt index 40dd5ca6f..249696ac9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerFragment.kt @@ -150,12 +150,14 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames item.channelName } setOnClickListener { - findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo - )) + findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo + ) + ) slidingLayout.minimize() } } @@ -210,10 +212,27 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames } )) .setNegativeButton(getString(R.string.no), null) - .setPositiveButton(getString(R.string.yes)) { _, _ -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) } + .setPositiveButton(getString(R.string.yes)) { _, _ -> + viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) + } .show() } else { - viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), setting, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) + viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + setting, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) } } } @@ -286,7 +305,15 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames override fun initialize() { super.initialize() - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) if ((prefs.getBoolean(C.PLAYER_GAMESBUTTON, true) || prefs.getBoolean(C.PLAYER_MENU_GAMES, false)) && !item.id.isNullOrBlank()) { viewModel.loadGamesList(TwitchApiHelper.getGQLHeaders(requireContext()), item.id) } @@ -315,11 +342,15 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames private fun playVideo(skipAccessToken: Boolean, playbackPosition: Long?) { if (skipAccessToken && !item.animatedPreviewURL.isNullOrBlank()) { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_VIDEO, bundleOf( - PlaybackService.ITEM to item, - PlaybackService.USING_PLAYLIST to false, - PlaybackService.PLAYBACK_POSITION to playbackPosition, - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_VIDEO, bundleOf( + PlaybackService.ITEM to item, + PlaybackService.USING_PLAYLIST to false, + PlaybackService.PLAYBACK_POSITION to playbackPosition, + ) + ), Bundle.EMPTY + ) } else { viewModel.load( gqlHeaders = TwitchApiHelper.getGQLHeaders(requireContext(), prefs.getBoolean(C.TOKEN_INCLUDE_TOKEN_VIDEO, true)), @@ -332,12 +363,16 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.result.collectLatest { if (it != null) { - player?.sendCustomCommand(SessionCommand(PlaybackService.START_VIDEO, bundleOf( - PlaybackService.ITEM to item, - PlaybackService.URI to it.toString(), - PlaybackService.USING_PLAYLIST to true, - PlaybackService.PLAYBACK_POSITION to playbackPosition, - )), Bundle.EMPTY) + player?.sendCustomCommand( + SessionCommand( + PlaybackService.START_VIDEO, bundleOf( + PlaybackService.ITEM to item, + PlaybackService.URI to it.toString(), + PlaybackService.USING_PLAYLIST to true, + PlaybackService.PLAYBACK_POSITION to playbackPosition, + ) + ), Bundle.EMPTY + ) viewModel.result.value = null } } @@ -348,7 +383,10 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames override fun onError(error: PlaybackException) { Log.e(tag, "Player error", error) - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_ERROR_CODE, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_ERROR_CODE, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { val responseCode = result.get().extras.getInt(PlaybackService.RESULT) @@ -372,7 +410,8 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames delay(1500L) try { player?.prepare() - } catch (e: Exception) {} + } catch (e: Exception) { + } } } } @@ -383,7 +422,9 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames } fun showVodGames() { - viewModel.gamesList.value?.let { PlayerGamesDialog.newInstance(it).show(childFragmentManager, "closeOnPip") } + viewModel.gamesList.value?.let { + PlayerGamesDialog.newInstance(it).show(childFragmentManager, "closeOnPip") + } } fun checkBookmark() { @@ -400,7 +441,10 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames override fun showDownloadDialog() { if (viewModel.loaded.value) { - player?.sendCustomCommand(SessionCommand(PlaybackService.GET_VIDEO_DOWNLOAD_INFO, Bundle.EMPTY), Bundle.EMPTY)?.let { result -> + player?.sendCustomCommand( + SessionCommand(PlaybackService.GET_VIDEO_DOWNLOAD_INFO, Bundle.EMPTY), + Bundle.EMPTY + )?.let { result -> result.addListener({ if (result.get().resultCode == SessionResult.RESULT_SUCCESS) { result.get().extras.getStringArray(PlaybackService.URLS_KEYS)?.let { keys -> @@ -441,13 +485,36 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames supportedCodecs = prefs.getString(C.TOKEN_SUPPORTED_CODECS, "av1,h265,h264"), enableIntegrity = prefs.getBoolean(C.ENABLE_INTEGRITY, false) ) - viewModel.isFollowingChannel(TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext(), true), requireContext().tokenPrefs().getString(C.USER_ID, null), requireContext().tokenPrefs().getString(C.USERNAME, null), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, item.channelId, item.channelLogin) + viewModel.isFollowingChannel( + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext(), true), + requireContext().tokenPrefs().getString(C.USER_ID, null), + requireContext().tokenPrefs().getString(C.USERNAME, null), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + item.channelId, + item.channelLogin + ) if ((prefs.getBoolean(C.PLAYER_GAMESBUTTON, true) || prefs.getBoolean(C.PLAYER_MENU_GAMES, false)) && !item.id.isNullOrBlank()) { viewModel.loadGamesList(TwitchApiHelper.getGQLHeaders(requireContext()), item.id) } } - "follow" -> viewModel.saveFollowChannel(requireContext().filesDir.path, TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId, item.channelLogin, item.channelName, item.channelLogo, requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false)) - "unfollow" -> viewModel.deleteFollowChannel(TwitchApiHelper.getGQLHeaders(requireContext(), true), prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, requireContext().tokenPrefs().getString(C.USER_ID, null), item.channelId) + "follow" -> viewModel.saveFollowChannel( + requireContext().filesDir.path, + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId, + item.channelLogin, + item.channelName, + item.channelLogo, + requireContext().prefs().getBoolean(C.LIVE_NOTIFICATIONS_ENABLED, false) + ) + "unfollow" -> viewModel.deleteFollowChannel( + TwitchApiHelper.getGQLHeaders(requireContext(), true), + prefs.getString(C.UI_FOLLOW_BUTTON, "0")?.toIntOrNull() ?: 0, + requireContext().tokenPrefs().getString(C.USER_ID, null), + item.channelId + ) } } } @@ -476,7 +543,13 @@ class VideoPlayerFragment : BasePlayerFragment(), HasDownloadDialog, PlayerGames private const val KEY_IGNORE_SAVED_POSITION = "ignoreSavedPosition" fun newInstance(video: Video, offset: Double? = null, ignoreSavedPosition: Boolean = false): VideoPlayerFragment { - return VideoPlayerFragment().apply { arguments = bundleOf(KEY_VIDEO to video, KEY_OFFSET to offset, KEY_IGNORE_SAVED_POSITION to ignoreSavedPosition) } + return VideoPlayerFragment().apply { + arguments = bundleOf( + KEY_VIDEO to video, + KEY_OFFSET to offset, + KEY_IGNORE_SAVED_POSITION to ignoreSavedPosition + ) + } } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerViewModel.kt index e2e7f9245..55adf8de5 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/player/video/VideoPlayerViewModel.kt @@ -33,7 +33,8 @@ class VideoPlayerViewModel @Inject constructor( notificationUsersRepository: NotificationUsersRepository, private val okHttpClient: OkHttpClient, private val playerRepository: PlayerRepository, - private val bookmarksRepository: BookmarksRepository) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { + private val bookmarksRepository: BookmarksRepository, +) : PlayerViewModel(repository, localFollowsChannel, shownNotificationsRepository, notificationUsersRepository, okHttpClient) { val result = MutableStateFlow(null) @@ -141,24 +142,26 @@ class VideoPlayerViewModel @Inject constructor( } catch (e: Exception) { null } - bookmarksRepository.saveBookmark(Bookmark( - videoId = video.id, - userId = video.channelId, - userLogin = video.channelLogin, - userName = video.channelName, - userType = userTypes?.type, - userBroadcasterType = userTypes?.broadcasterType, - userLogo = downloadedLogo, - gameId = video.gameId, - gameSlug = video.gameSlug, - gameName = video.gameName, - title = video.title, - createdAt = video.uploadDate, - thumbnail = downloadedThumbnail, - type = video.type, - duration = video.duration, - animatedPreviewURL = video.animatedPreviewURL - )) + bookmarksRepository.saveBookmark( + Bookmark( + videoId = video.id, + userId = video.channelId, + userLogin = video.channelLogin, + userName = video.channelName, + userType = userTypes?.type, + userBroadcasterType = userTypes?.broadcasterType, + userLogo = downloadedLogo, + gameId = video.gameId, + gameSlug = video.gameSlug, + gameName = video.gameName, + title = video.title, + createdAt = video.uploadDate, + thumbnail = downloadedThumbnail, + type = video.type, + duration = video.duration, + animatedPreviewURL = video.animatedPreviewURL + ) + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedMediaFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedMediaFragment.kt index 7aeea24da..d12e8be70 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedMediaFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedMediaFragment.kt @@ -94,7 +94,8 @@ class SavedMediaFragment : Fragment(), Scrollable, FragmentHost { super.onViewCreated(view, savedInstanceState) with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) toolbar.setupWithNavController(navController, appBarConfiguration) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerFragment.kt index ff2550f8c..01a6cd8c6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerFragment.kt @@ -93,7 +93,8 @@ class SavedPagerFragment : Fragment(), Scrollable, FragmentHost { super.onViewCreated(view, savedInstanceState) with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) toolbar.setupWithNavController(navController, appBarConfiguration) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerViewModel.kt index 979bd89e3..a82eb4e65 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/SavedPagerViewModel.kt @@ -20,7 +20,8 @@ import kotlin.math.max @HiltViewModel class SavedPagerViewModel @Inject constructor( @ApplicationContext private val applicationContext: Context, - private val offlineRepository: OfflineRepository) : ViewModel() { + private val offlineRepository: OfflineRepository, +) : ViewModel() { fun saveFolders(url: String) { viewModelScope.launch(Dispatchers.IO) { @@ -107,7 +108,7 @@ class SavedPagerViewModel @Inject constructor( gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, gameName = if (!gameName.isNullOrBlank()) gameName else null, duration = totalDuration, - uploadDate = if (uploadDate != null) uploadDate else null, + uploadDate = uploadDate, progress = 100, maxProgress = 100, status = OfflineVideo.STATUS_DOWNLOADED, @@ -176,23 +177,25 @@ class SavedPagerViewModel @Inject constructor( } } - offlineRepository.saveVideo(OfflineVideo( - url = url, - name = if (!title.isNullOrBlank()) title else null ?: fileName, - channelId = if (!channelId.isNullOrBlank()) channelId else null, - channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, - channelName = if (!channelName.isNullOrBlank()) channelName else null, - thumbnail = url, - gameId = if (!gameId.isNullOrBlank()) gameId else null, - gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, - gameName = if (!gameName.isNullOrBlank()) gameName else null, - uploadDate = if (uploadDate != null) uploadDate else null, - progress = 100, - maxProgress = 100, - status = OfflineVideo.STATUS_DOWNLOADED, - videoId = if (!id.isNullOrBlank()) id else null, - chatUrl = chatFile - )) + offlineRepository.saveVideo( + OfflineVideo( + url = url, + name = if (!title.isNullOrBlank()) title else null ?: fileName, + channelId = if (!channelId.isNullOrBlank()) channelId else null, + channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, + channelName = if (!channelName.isNullOrBlank()) channelName else null, + thumbnail = url, + gameId = if (!gameId.isNullOrBlank()) gameId else null, + gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, + gameName = if (!gameName.isNullOrBlank()) gameName else null, + uploadDate = uploadDate, + progress = 100, + maxProgress = 100, + status = OfflineVideo.STATUS_DOWNLOADED, + videoId = if (!id.isNullOrBlank()) id else null, + chatUrl = chatFile + ) + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksAdapter.kt index 6e14557bf..9963e89bf 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksAdapter.kt @@ -33,7 +33,8 @@ class BookmarksAdapter( private val refreshVideo: (String?) -> Unit, private val showDownloadDialog: (Video) -> Unit, private val vodIgnoreUser: (String) -> Unit, - private val deleteVideo: (Bookmark) -> Unit) : PagingDataAdapter( + private val deleteVideo: (Bookmark) -> Unit, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean = oldItem.id == newItem.id @@ -72,18 +73,23 @@ class BookmarksAdapter( inner class PagingViewHolder( private val binding: FragmentVideosListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Bookmark?) { with(binding) { if (item != null) { val context = fragment.requireContext() - val channelListener: (View) -> Unit = { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.userId, - channelLogin = item.userLogin, - channelName = item.userName, - channelLogo = item.userLogo, - updateLocal = true - )) } + val channelListener: (View) -> Unit = { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.userId, + channelLogin = item.userLogin, + channelName = item.userName, + channelLogo = item.userLogo, + updateLocal = true + ) + ) + } val gameListener: (View) -> Unit = { fragment.findNavController().navigate( if (context.prefs().getBoolean(C.UI_GAMEPAGER, true)) { @@ -106,25 +112,31 @@ class BookmarksAdapter( val ignore = ignored?.find { it.userId == item.userId } != null val userType = item.userType ?: item.userBroadcasterType root.setOnClickListener { - (fragment.activity as MainActivity).startVideo(Video( - id = item.videoId, - channelId = item.userId, - channelLogin = item.userLogin, - channelName = item.userName, - profileImageUrl = item.userLogo, - gameId = item.gameId, - gameSlug = item.gameSlug, - gameName = item.gameName, - title = item.title, - uploadDate = item.createdAt, - thumbnailUrl = item.thumbnail, - type = item.type, - duration = item.duration, - animatedPreviewURL = item.animatedPreviewURL, - ), position?.toDouble()) + (fragment.activity as MainActivity).startVideo( + Video( + id = item.videoId, + channelId = item.userId, + channelLogin = item.userLogin, + channelName = item.userName, + profileImageUrl = item.userLogo, + gameId = item.gameId, + gameSlug = item.gameSlug, + gameName = item.gameName, + title = item.title, + uploadDate = item.createdAt, + thumbnailUrl = item.thumbnail, + type = item.type, + duration = item.duration, + animatedPreviewURL = item.animatedPreviewURL, + ), position?.toDouble() + ) } root.setOnLongClickListener { deleteVideo(item); true } - thumbnail.loadImage(fragment, item.thumbnail, diskCacheStrategy = DiskCacheStrategy.NONE) + thumbnail.loadImage( + fragment, + item.thumbnail, + diskCacheStrategy = DiskCacheStrategy.NONE + ) if (item.createdAt != null) { val text = TwitchApiHelper.formatTimeString(context, item.createdAt) if (text != null) { @@ -142,7 +154,8 @@ class BookmarksAdapter( "" -> 14 "affiliate" -> 14 else -> 60 - }) + } + ) if (!time.isNullOrBlank()) { views.visible() views.text = context.getString(R.string.vod_time_left, time) @@ -169,14 +182,19 @@ class BookmarksAdapter( } else { type.gone() } - if (item.userLogo != null) { + if (item.userLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.userLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), diskCacheStrategy = DiskCacheStrategy.NONE) + userImage.loadImage( + fragment, + item.userLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), + diskCacheStrategy = DiskCacheStrategy.NONE, + ) userImage.setOnClickListener(channelListener) } else { userImage.gone() } - if (item.userName != null) { + if (item.userName != null) { username.visible() username.text = if (item.userLogin != null && !item.userLogin.equals(item.userName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { @@ -197,13 +215,13 @@ class BookmarksAdapter( } else { progressBar.gone() } - if (item.title != null) { + if (item.title != null) { title.visible() title.text = item.title.trim() } else { title.gone() } - if (item.gameName != null) { + if (item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -226,23 +244,25 @@ class BookmarksAdapter( } } setOnMenuItemClickListener { - when(it.itemId) { + when (it.itemId) { R.id.delete -> deleteVideo(item) - R.id.download -> showDownloadDialog(Video( - id = item.videoId, - channelId = item.userId, - channelLogin = item.userLogin, - channelName = item.userName, - profileImageUrl = item.userLogo, - gameId = item.gameId, - gameName = item.gameName, - title = item.title, - uploadDate = item.createdAt, - thumbnailUrl = item.thumbnail, - type = item.type, - duration = item.duration, - animatedPreviewURL = item.animatedPreviewURL, - )) + R.id.download -> showDownloadDialog( + Video( + id = item.videoId, + channelId = item.userId, + channelLogin = item.userLogin, + channelName = item.userName, + profileImageUrl = item.userLogo, + gameId = item.gameId, + gameName = item.gameName, + title = item.title, + uploadDate = item.createdAt, + thumbnailUrl = item.thumbnail, + type = item.type, + duration = item.duration, + animatedPreviewURL = item.animatedPreviewURL, + ) + ) R.id.vodIgnore -> item.userId?.let { id -> vodIgnoreUser(id) } R.id.refresh -> refreshVideo(item.videoId) else -> menu.close() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksFragment.kt index c87313eb3..765d0d005 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksFragment.kt @@ -45,7 +45,11 @@ class BookmarksFragment : PagedListFragment(), Scrollable { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -53,7 +57,12 @@ class BookmarksFragment : PagedListFragment(), Scrollable { } } pagingAdapter = BookmarksAdapter(this, { - viewModel.updateVideo(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), it) + viewModel.updateVideo( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + it + ) }, { DownloadDialog.newInstance(it).show(childFragmentManager, null) }, { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksViewModel.kt index 491773b3d..a11598408 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/bookmarks/BookmarksViewModel.kt @@ -28,7 +28,8 @@ class BookmarksViewModel @Inject internal constructor( private val bookmarksRepository: BookmarksRepository, playerRepository: PlayerRepository, private val vodBookmarkIgnoredUsersRepository: VodBookmarkIgnoredUsersRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val integrity = MutableStateFlow(null) @@ -63,13 +64,19 @@ class BookmarksViewModel @Inject internal constructor( if (!updatedUsers) { viewModelScope.launch { try { - val allIds = bookmarksRepository.loadBookmarks().mapNotNull { bookmark -> bookmark.userId.takeUnless { it == null || vodBookmarkIgnoredUsersRepository.loadUsers().contains(VodBookmarkIgnoredUser(it)) } } + val allIds = bookmarksRepository.loadBookmarks().mapNotNull { bookmark -> + bookmark.userId.takeUnless { + it == null || vodBookmarkIgnoredUsersRepository.loadUsers().contains(VodBookmarkIgnoredUser(it)) + } + } if (allIds.isNotEmpty()) { for (ids in allIds.chunked(100)) { val users = repository.loadUserTypes(ids, helixHeaders, gqlHeaders) if (users != null) { for (user in users) { - val bookmarks = user.channelId?.let { bookmarksRepository.getBookmarksByUserId(it) } + val bookmarks = user.channelId?.let { + bookmarksRepository.getBookmarksByUserId(it) + } if (bookmarks != null) { for (bookmark in bookmarks) { if (user.type != bookmark.userType || user.broadcasterType != bookmark.userBroadcasterType) { @@ -82,7 +89,8 @@ class BookmarksViewModel @Inject internal constructor( } } } - }} + } + } updatedUsers = true } catch (e: Exception) { if (e.message == "failed integrity check" && integrity.value == null) { @@ -121,24 +129,26 @@ class BookmarksViewModel @Inject internal constructor( path } } - bookmarksRepository.updateBookmark(Bookmark( - videoId = bookmark.videoId, - userId = video.channelId ?: bookmark.userId, - userLogin = video.channelLogin ?: bookmark.userLogin, - userName = video.channelName ?: bookmark.userName, - userType = bookmark.userType, - userBroadcasterType = bookmark.userBroadcasterType, - userLogo = bookmark.userLogo, - gameId = video.gameId ?: bookmark.gameId, - gameSlug = video.gameSlug ?: bookmark.gameSlug, - gameName = video.gameName ?: bookmark.gameName, - title = video.title ?: bookmark.title, - createdAt = video.uploadDate ?: bookmark.createdAt, - thumbnail = downloadedThumbnail, - type = video.type ?: bookmark.type, - duration = video.duration ?: bookmark.duration, - animatedPreviewURL = video.animatedPreviewURL ?: bookmark.animatedPreviewURL - )) + bookmarksRepository.updateBookmark( + Bookmark( + videoId = bookmark.videoId, + userId = video.channelId ?: bookmark.userId, + userLogin = video.channelLogin ?: bookmark.userLogin, + userName = video.channelName ?: bookmark.userName, + userType = bookmark.userType, + userBroadcasterType = bookmark.userBroadcasterType, + userLogo = bookmark.userLogo, + gameId = video.gameId ?: bookmark.gameId, + gameSlug = video.gameSlug ?: bookmark.gameSlug, + gameName = video.gameName ?: bookmark.gameName, + title = video.title ?: bookmark.title, + createdAt = video.uploadDate ?: bookmark.createdAt, + thumbnail = downloadedThumbnail, + type = video.type ?: bookmark.type, + duration = video.duration ?: bookmark.duration, + animatedPreviewURL = video.animatedPreviewURL ?: bookmark.animatedPreviewURL + ) + ) } } catch (e: Exception) { if (e.message == "failed integrity check" && integrity.value == null) { @@ -164,7 +174,8 @@ class BookmarksViewModel @Inject internal constructor( bookmark.title != video.title || bookmark.createdAt != video.uploadDate || bookmark.type != video.type || - bookmark.duration != video.duration)) { + bookmark.duration != video.duration) + ) { val downloadedThumbnail = video.thumbnail.takeIf { !it.isNullOrBlank() }?.let { File(filesDir, "thumbnails").mkdir() val path = filesDir + File.separator + "thumbnails" + File.separator + video.id @@ -183,24 +194,26 @@ class BookmarksViewModel @Inject internal constructor( } path } - bookmarksRepository.updateBookmark(Bookmark( - videoId = bookmark.videoId, - userId = video.channelId ?: bookmark.userId, - userLogin = video.channelLogin ?: bookmark.userLogin, - userName = video.channelName ?: bookmark.userName, - userType = bookmark.userType, - userBroadcasterType = bookmark.userBroadcasterType, - userLogo = bookmark.userLogo, - gameId = bookmark.gameId, - gameSlug = bookmark.gameSlug, - gameName = bookmark.gameName, - title = video.title ?: bookmark.title, - createdAt = video.uploadDate ?: bookmark.createdAt, - thumbnail = downloadedThumbnail, - type = video.type ?: bookmark.type, - duration = video.duration ?: bookmark.duration, - animatedPreviewURL = video.animatedPreviewURL ?: bookmark.animatedPreviewURL - )) + bookmarksRepository.updateBookmark( + Bookmark( + videoId = bookmark.videoId, + userId = video.channelId ?: bookmark.userId, + userLogin = video.channelLogin ?: bookmark.userLogin, + userName = video.channelName ?: bookmark.userName, + userType = bookmark.userType, + userBroadcasterType = bookmark.userBroadcasterType, + userLogo = bookmark.userLogo, + gameId = bookmark.gameId, + gameSlug = bookmark.gameSlug, + gameName = bookmark.gameName, + title = video.title ?: bookmark.title, + createdAt = video.uploadDate ?: bookmark.createdAt, + thumbnail = downloadedThumbnail, + type = video.type ?: bookmark.type, + duration = video.duration ?: bookmark.duration, + animatedPreviewURL = video.animatedPreviewURL ?: bookmark.animatedPreviewURL + ) + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsAdapter.kt index 759a5b219..6b8f4e86b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsAdapter.kt @@ -40,7 +40,8 @@ class DownloadsAdapter( private val moveVideo: (OfflineVideo) -> Unit, private val updateChatUrl: (OfflineVideo) -> Unit, private val shareVideo: (OfflineVideo) -> Unit, - private val deleteVideo: (OfflineVideo) -> Unit) : PagingDataAdapter( + private val deleteVideo: (OfflineVideo) -> Unit, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: OfflineVideo, newItem: OfflineVideo): Boolean { return oldItem.id == newItem.id @@ -62,18 +63,23 @@ class DownloadsAdapter( inner class PagingViewHolder( private val binding: FragmentDownloadsListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: OfflineVideo?) { with(binding) { if (item != null) { val context = fragment.requireContext() - val channelListener: (View) -> Unit = { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - updateLocal = true - )) } + val channelListener: (View) -> Unit = { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + updateLocal = true + ) + ) + } val gameListener: (View) -> Unit = { fragment.findNavController().navigate( if (context.prefs().getBoolean(C.UI_GAMEPAGER, true)) { @@ -95,7 +101,11 @@ class DownloadsAdapter( (fragment.activity as MainActivity).startOfflineVideo(item) } root.setOnLongClickListener { deleteVideo(item); true } - thumbnail.loadImage(fragment, item.thumbnail, diskCacheStrategy = DiskCacheStrategy.NONE) + thumbnail.loadImage( + fragment, + item.thumbnail, + diskCacheStrategy = DiskCacheStrategy.NONE + ) item.uploadDate?.let { date.visible() date.text = context.getString(R.string.uploaded_date, TwitchApiHelper.formatTime(context, it)) @@ -119,14 +129,19 @@ class DownloadsAdapter( } else { type.gone() } - if (item.channelLogo != null) { + if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), diskCacheStrategy = DiskCacheStrategy.NONE) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true), + diskCacheStrategy = DiskCacheStrategy.NONE + ) userImage.setOnClickListener(channelListener) } else { userImage.gone() } - if (item.channelName != null) { + if (item.channelName != null) { username.visible() username.text = if (item.channelLogin != null && !item.channelLogin.equals(item.channelName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { @@ -147,7 +162,7 @@ class DownloadsAdapter( } ?: { title.gone() } - if (item.gameName != null) { + if (item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -210,11 +225,13 @@ class DownloadsAdapter( else -> { menu.findItem(R.id.moveVideo).apply { isVisible = true - title = context.getString(if (item.url?.toUri()?.scheme == ContentResolver.SCHEME_CONTENT) { - R.string.move_to_app_storage - } else { - R.string.move_to_shared_storage - }) + title = context.getString( + if (item.url?.toUri()?.scheme == ContentResolver.SCHEME_CONTENT) { + R.string.move_to_app_storage + } else { + R.string.move_to_shared_storage + } + ) } if (item.url?.endsWith(".m3u8") == true) { menu.findItem(R.id.convertVideo).isVisible = true @@ -226,7 +243,7 @@ class DownloadsAdapter( } } setOnMenuItemClickListener { - when(it.itemId) { + when (it.itemId) { R.id.stopDownload -> stopDownload(item) R.id.resumeDownload -> resumeDownload(item) R.id.convertVideo -> convertVideo(item) @@ -268,7 +285,12 @@ class DownloadsAdapter( chatDownloadProgress.gone() } status.visible() - if (item.status == OfflineVideo.STATUS_DOWNLOADING || item.status == OfflineVideo.STATUS_BLOCKED || item.status == OfflineVideo.STATUS_QUEUED || item.status == OfflineVideo.STATUS_QUEUED_WIFI || item.status == OfflineVideo.STATUS_WAITING_FOR_STREAM) { + if (item.status == OfflineVideo.STATUS_DOWNLOADING || + item.status == OfflineVideo.STATUS_BLOCKED || + item.status == OfflineVideo.STATUS_QUEUED || + item.status == OfflineVideo.STATUS_QUEUED_WIFI || + item.status == OfflineVideo.STATUS_WAITING_FOR_STREAM + ) { checkDownloadStatus(item) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsFragment.kt index 94a4cb893..8dbf84faa 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsFragment.kt @@ -101,7 +101,9 @@ class DownloadsFragment : PagedListFragment(), Scrollable { }, { if (it.live) { if (it.status != OfflineVideo.STATUS_PENDING) { - it.channelLogin?.let { channelLogin -> WorkManager.getInstance(requireContext()).cancelUniqueWork(channelLogin) } + it.channelLogin?.let { channelLogin -> + WorkManager.getInstance(requireContext()).cancelUniqueWork(channelLogin) + } } else { viewModel.finishDownload(it) } @@ -119,11 +121,13 @@ class DownloadsFragment : PagedListFragment(), Scrollable { .setInputData(workDataOf(StreamDownloadWorker.KEY_VIDEO_ID to it.id)) .setConstraints( Constraints.Builder() - .setRequiredNetworkType(if (requireContext().prefs().getBoolean(C.DOWNLOAD_WIFI_ONLY, false)) { - NetworkType.UNMETERED - } else { - NetworkType.CONNECTED - }) + .setRequiredNetworkType( + if (requireContext().prefs().getBoolean(C.DOWNLOAD_WIFI_ONLY, false)) { + NetworkType.UNMETERED + } else { + NetworkType.CONNECTED + } + ) .build() ) .build() @@ -139,11 +143,13 @@ class DownloadsFragment : PagedListFragment(), Scrollable { .addTag(it.id.toString()) .setConstraints( Constraints.Builder() - .setRequiredNetworkType(if (requireContext().prefs().getBoolean(C.DOWNLOAD_WIFI_ONLY, false)) { - NetworkType.UNMETERED - } else { - NetworkType.CONNECTED - }) + .setRequiredNetworkType( + if (requireContext().prefs().getBoolean(C.DOWNLOAD_WIFI_ONLY, false)) { + NetworkType.UNMETERED + } else { + NetworkType.CONNECTED + } + ) .build() ) .build() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsViewModel.kt index 04ecde761..64113bac1 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/saved/downloads/DownloadsViewModel.kt @@ -37,7 +37,8 @@ import kotlin.math.max @HiltViewModel class DownloadsViewModel @Inject internal constructor( @ApplicationContext private val applicationContext: Context, - private val repository: OfflineRepository) : ViewModel() { + private val repository: OfflineRepository, +) : ViewModel() { var selectedVideo: OfflineVideo? = null private val videosInUse = mutableListOf() diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerFragment.kt index bfd135e30..8ccc3cfbc 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerFragment.kt @@ -67,7 +67,11 @@ class SearchPagerFragment : BaseNetworkFragment(), FragmentHost { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.integrity.collectLatest { - if (it != null && it != "done" && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if (it != null && + it != "done" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, it) viewModel.integrity.value = "done" } @@ -213,8 +217,16 @@ class SearchPagerFragment : BaseNetworkFragment(), FragmentHost { private fun viewUserResult() { userResult?.let { when (it.first) { - 0 -> findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment(channelId = it.second)) - 1 -> findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment(channelLogin = it.second)) + 0 -> findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = it.second + ) + ) + 1 -> findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelLogin = it.second + ) + ) else -> {} } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerViewModel.kt index 52c098d58..e7f287608 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/SearchPagerViewModel.kt @@ -10,7 +10,8 @@ import javax.inject.Inject @HiltViewModel class SearchPagerViewModel @Inject constructor( - private val repository: ApiRepository) : ViewModel() { + private val repository: ApiRepository, +) : ViewModel() { val integrity = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchAdapter.kt index db4e903b7..4ac9895da 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchAdapter.kt @@ -19,7 +19,8 @@ import com.github.andreyasadchy.xtra.util.prefs import com.github.andreyasadchy.xtra.util.visible class ChannelSearchAdapter( - private val fragment: Fragment) : PagingDataAdapter( + private val fragment: Fragment, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.channelId == newItem.channelId @@ -38,22 +39,29 @@ class ChannelSearchAdapter( inner class PagingViewHolder( private val binding: FragmentSearchChannelsListItemBinding, - private val fragment: Fragment): RecyclerView.ViewHolder(binding.root) { + private val fragment: Fragment, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: User?) { with(binding) { if (item != null) { val context = fragment.requireContext() root.setOnClickListener { - fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - )) + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + ) + ) } if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) } else { userImage.gone() } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchFragment.kt index 4973307dc..413534659 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchFragment.kt @@ -57,15 +57,22 @@ class ChannelSearchFragment : PagedListFragment(), Searchable { pagingAdapter.loadStateFlow.collectLatest { loadState -> progressBar.isVisible = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount == 0 nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 && viewModel.query.value.isNotBlank() - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchViewModel.kt index 252648d3e..8eaac2aa8 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/channels/ChannelSearchViewModel.kt @@ -26,7 +26,8 @@ class ChannelSearchViewModel @Inject constructor( @ApplicationContext applicationContext: Context, private val graphQLRepository: GraphQLRepository, private val helix: HelixApi, - private val apolloClient: ApolloClient) : ViewModel() { + private val apolloClient: ApolloClient, +) : ViewModel() { private val _query = MutableStateFlow("") val query: StateFlow = _query diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchFragment.kt index 8c7c4ac63..b1dd42a2c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchFragment.kt @@ -58,15 +58,22 @@ class GameSearchFragment : PagedListFragment(), Searchable { pagingAdapter.loadStateFlow.collectLatest { loadState -> progressBar.isVisible = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount == 0 nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 && viewModel.query.value.isNotBlank() - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchViewModel.kt index 0630aa340..efe50feb3 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/games/GameSearchViewModel.kt @@ -26,7 +26,8 @@ class GameSearchViewModel @Inject constructor( @ApplicationContext applicationContext: Context, private val graphQLRepository: GraphQLRepository, private val helix: HelixApi, - private val apolloClient: ApolloClient) : ViewModel() { + private val apolloClient: ApolloClient, +) : ViewModel() { private val _query = MutableStateFlow("") val query: StateFlow = _query diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchFragment.kt index 2d9f68a47..30340554a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchFragment.kt @@ -63,15 +63,22 @@ class StreamSearchFragment : PagedListFragment(), Searchable { pagingAdapter.loadStateFlow.collectLatest { loadState -> progressBar.isVisible = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount == 0 nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 && viewModel.query.value.isNotBlank() - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchViewModel.kt index 04019f7bc..a86113dc6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/streams/StreamSearchViewModel.kt @@ -24,7 +24,8 @@ import javax.inject.Inject class StreamSearchViewModel @Inject constructor( @ApplicationContext applicationContext: Context, private val helix: HelixApi, - private val apolloClient: ApolloClient) : ViewModel() { + private val apolloClient: ApolloClient, +) : ViewModel() { private val _query = MutableStateFlow("") val query: StateFlow = _query diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchAdapter.kt index 692db6e5f..4045dce5a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchAdapter.kt @@ -20,7 +20,8 @@ import com.github.andreyasadchy.xtra.util.visible class TagSearchAdapter( private val fragment: Fragment, - private val args: TagSearchFragmentArgs) : PagingDataAdapter( + private val args: TagSearchFragmentArgs, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Tag, newItem: Tag): Boolean = oldItem.name == newItem.name @@ -40,7 +41,8 @@ class TagSearchAdapter( inner class PagingViewHolder( private val binding: FragmentSearchChannelsListItemBinding, private val fragment: Fragment, - private val args: TagSearchFragmentArgs): RecyclerView.ViewHolder(binding.root) { + private val args: TagSearchFragmentArgs, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Tag?) { with(binding) { if (item != null) { @@ -54,9 +56,11 @@ class TagSearchAdapter( if (item.scope == "CATEGORY") { if (item.id != null) { root.setOnClickListener { - fragment.findNavController().navigate(GamesFragmentDirections.actionGlobalGamesFragment( - tags = arrayOf(item.id) - )) + fragment.findNavController().navigate( + GamesFragmentDirections.actionGlobalGamesFragment( + tags = arrayOf(item.id) + ) + ) } } } else { @@ -83,9 +87,11 @@ class TagSearchAdapter( } } else { root.setOnClickListener { - fragment.findNavController().navigate(TopFragmentDirections.actionGlobalTopFragment( - tags = arrayOf(item.name) - )) + fragment.findNavController().navigate( + TopFragmentDirections.actionGlobalTopFragment( + tags = arrayOf(item.name) + ) + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchFragment.kt index 7e37f9531..3959ac04e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchFragment.kt @@ -84,15 +84,22 @@ class TagSearchFragment : PagedListFragment() { pagingAdapter.loadStateFlow.collectLatest { loadState -> progressBar.isVisible = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount == 0 nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 && viewModel.query.value.isNotBlank() - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } with(binding) { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchViewModel.kt index 12f42500f..6b4942103 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/tags/TagSearchViewModel.kt @@ -22,7 +22,8 @@ import javax.inject.Inject class TagSearchViewModel @Inject constructor( @ApplicationContext applicationContext: Context, private val graphQLRepository: GraphQLRepository, - savedStateHandle: SavedStateHandle) : ViewModel() { + savedStateHandle: SavedStateHandle, +) : ViewModel() { private val args = TagSearchFragmentArgs.fromSavedStateHandle(savedStateHandle) private val _query = MutableStateFlow("") @@ -37,7 +38,8 @@ class TagSearchViewModel @Inject constructor( gqlHeaders = TwitchApiHelper.getGQLHeaders(applicationContext), getGameTags = args.getGameTags, query = query, - api = graphQLRepository) + api = graphQLRepository + ) }.flow }.cachedIn(viewModelScope) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchFragment.kt index acaa6b6db..8f5f4127c 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchFragment.kt @@ -46,7 +46,12 @@ class VideoSearchFragment : BaseVideosFragment(), Searchable { showDownloadDialog() }, { lastSelectedItem = it - viewModel.saveBookmark(requireContext().filesDir.path, TwitchApiHelper.getHelixHeaders(requireContext()), TwitchApiHelper.getGQLHeaders(requireContext()), it) + viewModel.saveBookmark( + requireContext().filesDir.path, + TwitchApiHelper.getHelixHeaders(requireContext()), + TwitchApiHelper.getGQLHeaders(requireContext()), + it + ) }) setAdapter(binding.recyclerView, pagingAdapter) } @@ -65,15 +70,22 @@ class VideoSearchFragment : BaseVideosFragment(), Searchable { pagingAdapter.loadStateFlow.collectLatest { loadState -> progressBar.isVisible = loadState.refresh is LoadState.Loading && pagingAdapter.itemCount == 0 nothingHere.isVisible = loadState.refresh !is LoadState.Loading && pagingAdapter.itemCount == 0 && viewModel.query.value.isNotBlank() - if ((loadState.refresh as? LoadState.Error ?: loadState.append as? LoadState.Error ?: loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && - requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true)) { + if ((loadState.refresh as? LoadState.Error ?: + loadState.append as? LoadState.Error ?: + loadState.prepend as? LoadState.Error)?.error?.message == "failed integrity check" && + requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } } } } } - if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && TwitchApiHelper.isIntegrityTokenExpired(requireContext())) { + if (requireContext().prefs().getBoolean(C.ENABLE_INTEGRITY, false) && + requireContext().prefs().getBoolean(C.USE_WEBVIEW_INTEGRITY, true) && + TwitchApiHelper.isIntegrityTokenExpired(requireContext()) + ) { IntegrityDialog.show(childFragmentManager, "refresh") } initializeVideoAdapter(viewModel, pagingAdapter as BaseVideosAdapter) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchViewModel.kt index 90491df5d..35270613b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/search/videos/VideoSearchViewModel.kt @@ -32,7 +32,8 @@ class VideoSearchViewModel @Inject constructor( bookmarksRepository: BookmarksRepository, okHttpClient: OkHttpClient, private val graphQLRepository: GraphQLRepository, - private val apolloClient: ApolloClient) : BaseVideosViewModel(playerRepository, bookmarksRepository, repository, okHttpClient) { + private val apolloClient: ApolloClient, +) : BaseVideosViewModel(playerRepository, bookmarksRepository, repository, okHttpClient) { private val _query = MutableStateFlow("") val query: StateFlow = _query diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsActivity.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsActivity.kt index 2e5697e90..1cd51c6df 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsActivity.kt @@ -1,7 +1,6 @@ package com.github.andreyasadchy.xtra.ui.settings import android.Manifest -import android.app.Activity import android.app.admin.DevicePolicyManager import android.content.ComponentName import android.content.Intent @@ -119,17 +118,17 @@ class SettingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) changed = savedInstanceState?.getBoolean(KEY_CHANGED) == true if (changed) { - requireActivity().setResult(Activity.RESULT_OK) + requireActivity().setResult(RESULT_OK) } backupResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { result.data?.data?.let { viewModel.backupSettings(it.toString()) } } } restoreResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { val list = mutableListOf() result.data?.clipData?.let { clipData -> for (i in 0 until clipData.itemCount) { @@ -180,9 +179,14 @@ class SettingsActivity : AppCompatActivity() { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.updateUrl.collectLatest { if (it != null) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !requireContext().prefs().getBoolean(C.UPDATE_USE_BROWSER, false) && - !requireContext().packageManager.canRequestPackageInstalls()) { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:${requireContext().packageName}")) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && + !requireContext().prefs().getBoolean(C.UPDATE_USE_BROWSER, false) && + !requireContext().packageManager.canRequestPackageInstalls() + ) { + val intent = Intent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + Uri.parse("package:${requireContext().packageName}") + ) if (intent.resolveActivity(requireContext().packageManager) != null) { requireContext().startActivity(intent) } @@ -248,13 +252,15 @@ class SettingsActivity : AppCompatActivity() { } } setOnPreferenceChangeListener { _, value -> - AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags( - if (value.toString() == "auto") { - null - } else { - value.toString() - } - )) + AppCompatDelegate.setApplicationLocales( + LocaleListCompat.forLanguageTags( + if (value.toString() == "auto") { + null + } else { + value.toString() + } + ) + ) true } } @@ -370,9 +376,15 @@ class SettingsActivity : AppCompatActivity() { } findPreference("update_check_enabled")?.setOnPreferenceChangeListener { _, newValue -> - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && newValue == true && !requireContext().prefs().getBoolean(C.UPDATE_USE_BROWSER, false) && - !requireContext().packageManager.canRequestPackageInstalls()) { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:${requireContext().packageName}")) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && + newValue == true && + !requireContext().prefs().getBoolean(C.UPDATE_USE_BROWSER, false) && + !requireContext().packageManager.canRequestPackageInstalls() + ) { + val intent = Intent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + Uri.parse("package:${requireContext().packageName}") + ) if (intent.resolveActivity(requireContext().packageManager) != null) { requireContext().startActivity(intent) } @@ -389,9 +401,15 @@ class SettingsActivity : AppCompatActivity() { } findPreference("update_use_browser")?.setOnPreferenceChangeListener { _, newValue -> - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && newValue == false && requireContext().prefs().getBoolean(C.UPDATE_CHECK_ENABLED, false) && - !requireContext().packageManager.canRequestPackageInstalls()) { - val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:${requireContext().packageName}")) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.UPSIDE_DOWN_CAKE && + newValue == false && + requireContext().prefs().getBoolean(C.UPDATE_CHECK_ENABLED, false) && + !requireContext().packageManager.canRequestPackageInstalls() + ) { + val intent = Intent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + Uri.parse("package:${requireContext().packageName}") + ) if (intent.resolveActivity(requireContext().packageManager) != null) { requireContext().startActivity(intent) } @@ -423,7 +441,8 @@ class SettingsActivity : AppCompatActivity() { findPreference("live_notifications_enabled")?.setOnPreferenceChangeListener { _, newValue -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ActivityCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED + ) { ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1) } viewModel.toggleNotifications( @@ -459,7 +478,7 @@ class SettingsActivity : AppCompatActivity() { private fun setResult() { if (!changed) { changed = true - requireActivity().setResult(Activity.RESULT_OK) + requireActivity().setResult(RESULT_OK) } } @@ -476,7 +495,7 @@ class SettingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) changed = savedInstanceState?.getBoolean(SettingsFragment.KEY_CHANGED) == true if (changed) { - requireActivity().setResult(Activity.RESULT_OK) + requireActivity().setResult(RESULT_OK) } } @@ -785,7 +804,10 @@ class SettingsActivity : AppCompatActivity() { newId++ view.addView(TextView(requireContext()).apply { text = entry.key - layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { setMargins(context.convertDpToPixels(10F), context.convertDpToPixels(3F), 0, context.convertDpToPixels(3F)) } context.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.textAppearanceTitleMedium)).use { @@ -793,12 +815,14 @@ class SettingsActivity : AppCompatActivity() { } }) val list = (requireContext().prefs().getString(entry.value.first, null)?.split(',') ?: entry.value.second).map { - Pair(when (it) { - C.HELIX -> requireContext().getString(R.string.api_helix) - C.GQL -> requireContext().getString(R.string.api_gql) - C.GQL_PERSISTED_QUERY -> requireContext().getString(R.string.api_gql_persisted_query) - else -> "" - }, it) + Pair( + when (it) { + C.HELIX -> requireContext().getString(R.string.api_helix) + C.GQL -> requireContext().getString(R.string.api_gql) + C.GQL_PERSISTED_QUERY -> requireContext().getString(R.string.api_gql_persisted_query) + else -> "" + }, it + ) } view.addView((inflater.inflate(R.layout.drag_list_layout, container, false) as DragListView).apply { id = newId diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsViewModel.kt index d1c0d2143..5f0176b30 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/settings/SettingsViewModel.kt @@ -64,7 +64,8 @@ class SettingsViewModel @Inject constructor( private val helixApi: HelixApi, private val appDatabase: AppDatabase, private val okHttpClient: OkHttpClient, - private val json: Json) : ViewModel() { + private val json: Json, +) : ViewModel() { val updateUrl = MutableSharedFlow() @@ -149,24 +150,26 @@ class SettingsViewModel @Inject constructor( } } - offlineRepository.saveVideo(OfflineVideo( - url = playlistFile.path, - name = if (!title.isNullOrBlank()) title else null ?: file.name, - channelId = if (!channelId.isNullOrBlank()) channelId else null, - channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, - channelName = if (!channelName.isNullOrBlank()) channelName else null, - thumbnail = file.path + File.separator + segments.getOrNull(max(0, (segments.size / 2) - 1))?.uri, - gameId = if (!gameId.isNullOrBlank()) gameId else null, - gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, - gameName = if (!gameName.isNullOrBlank()) gameName else null, - duration = totalDuration, - uploadDate = if (uploadDate != null) uploadDate else null, - progress = 100, - maxProgress = 100, - status = OfflineVideo.STATUS_DOWNLOADED, - videoId = if (!id.isNullOrBlank()) id else null, - chatUrl = chatFile - )) + offlineRepository.saveVideo( + OfflineVideo( + url = playlistFile.path, + name = if (!title.isNullOrBlank()) title else null ?: file.name, + channelId = if (!channelId.isNullOrBlank()) channelId else null, + channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, + channelName = if (!channelName.isNullOrBlank()) channelName else null, + thumbnail = file.path + File.separator + segments.getOrNull(max(0, (segments.size / 2) - 1))?.uri, + gameId = if (!gameId.isNullOrBlank()) gameId else null, + gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, + gameName = if (!gameName.isNullOrBlank()) gameName else null, + duration = totalDuration, + uploadDate = uploadDate, + progress = 100, + maxProgress = 100, + status = OfflineVideo.STATUS_DOWNLOADED, + videoId = if (!id.isNullOrBlank()) id else null, + chatUrl = chatFile + ) + ) } } } else if (file.isFile && (file.name.endsWith(".mp4") || file.name.endsWith(".ts"))) { @@ -218,23 +221,25 @@ class SettingsViewModel @Inject constructor( } } - offlineRepository.saveVideo(OfflineVideo( - url = file.path, - name = if (!title.isNullOrBlank()) title else null ?: fileName, - channelId = if (!channelId.isNullOrBlank()) channelId else null, - channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, - channelName = if (!channelName.isNullOrBlank()) channelName else null, - thumbnail = file.path, - gameId = if (!gameId.isNullOrBlank()) gameId else null, - gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, - gameName = if (!gameName.isNullOrBlank()) gameName else null, - uploadDate = if (uploadDate != null) uploadDate else null, - progress = 100, - maxProgress = 100, - status = OfflineVideo.STATUS_DOWNLOADED, - videoId = if (!id.isNullOrBlank()) id else null, - chatUrl = chatFile - )) + offlineRepository.saveVideo( + OfflineVideo( + url = file.path, + name = if (!title.isNullOrBlank()) title else null ?: fileName, + channelId = if (!channelId.isNullOrBlank()) channelId else null, + channelLogin = if (!channelLogin.isNullOrBlank()) channelLogin else null, + channelName = if (!channelName.isNullOrBlank()) channelName else null, + thumbnail = file.path, + gameId = if (!gameId.isNullOrBlank()) gameId else null, + gameSlug = if (!gameSlug.isNullOrBlank()) gameSlug else null, + gameName = if (!gameName.isNullOrBlank()) gameName else null, + uploadDate = uploadDate, + progress = 100, + maxProgress = 100, + status = OfflineVideo.STATUS_DOWNLOADED, + videoId = if (!id.isNullOrBlank()) id else null, + chatUrl = chatFile + ) + ) } } } @@ -271,14 +276,23 @@ class SettingsViewModel @Inject constructor( okHttpClient.newCall(Request.Builder().url(url).build()).execute().use { response -> if (response.isSuccessful) { val packageInstaller = applicationContext.packageManager.packageInstaller - val sessionId = packageInstaller.createSession(PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)) + val sessionId = packageInstaller.createSession( + PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + ) val session = packageInstaller.openSession(sessionId) session.openWrite("package", 0, -1).sink().buffer().use { sink -> sink.writeAll(response.body.source()) } - session.commit(PendingIntent.getActivity(applicationContext, 0, Intent(applicationContext, MainActivity::class.java).apply { - setAction(MainActivity.INTENT_INSTALL_UPDATE) - }, PendingIntent.FLAG_MUTABLE).intentSender) + session.commit( + PendingIntent.getActivity( + applicationContext, + 0, + Intent(applicationContext, MainActivity::class.java).apply { + setAction(MainActivity.INTENT_INSTALL_UPDATE) + }, + PendingIntent.FLAG_MUTABLE + ).intentSender + ) session.close() } } @@ -326,9 +340,11 @@ class SettingsViewModel @Inject constructor( database.sink().buffer().use { sink -> sink.writeAll(applicationContext.contentResolver.openInputStream(url.toUri())!!.source().buffer()) } - applicationContext.startActivity(Intent(applicationContext, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - }) + applicationContext.startActivity( + Intent(applicationContext, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + ) exitProcess(0) } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsAdapter.kt index f52005d9d..8c8890ff6 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsAdapter.kt @@ -34,7 +34,8 @@ import com.github.andreyasadchy.xtra.util.visible class StreamsAdapter( private val fragment: Fragment, private val args: GamePagerFragmentArgs? = null, - private val hideGame: Boolean = false) : PagingDataAdapter( + private val hideGame: Boolean = false, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Stream, newItem: Stream): Boolean = oldItem.id == newItem.id @@ -58,18 +59,23 @@ class StreamsAdapter( private val binding: FragmentStreamsListItemBinding, private val fragment: Fragment, private val args: GamePagerFragmentArgs?, - private val hideGame: Boolean): RecyclerView.ViewHolder(binding.root) { + private val hideGame: Boolean, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Stream?) { with(binding) { if (item != null) { val context = fragment.requireContext() - val channelListener: (View) -> Unit = { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - streamId = item.id - )) } + val channelListener: (View) -> Unit = { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + streamId = item.id + ) + ) + } val gameListener: (View) -> Unit = { fragment.findNavController().navigate( if (context.prefs().getBoolean(C.UI_GAMEPAGER, true)) { @@ -90,14 +96,18 @@ class StreamsAdapter( root.setOnClickListener { (fragment.activity as MainActivity).startStream(item) } - if (item.channelLogo != null) { + if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) userImage.setOnClickListener(channelListener) } else { userImage.gone() } - if (item.channelName != null) { + if (item.channelName != null) { username.visible() username.text = if (item.channelLogin != null && !item.channelLogin.equals(item.channelName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { @@ -112,13 +122,13 @@ class StreamsAdapter( } else { username.gone() } - if (item.title != null && item.title != "") { + if (item.title != null && item.title != "") { title.visible() title.text = item.title?.trim() } else { title.gone() } - if (!hideGame && item.gameName != null) { + if (!hideGame && item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -127,7 +137,12 @@ class StreamsAdapter( } if (item.thumbnailUrl != null) { thumbnail.visible() - thumbnail.loadImage(fragment, item.thumbnail, true, diskCacheStrategy = DiskCacheStrategy.NONE) + thumbnail.loadImage( + fragment, + item.thumbnail, + changes = true, + diskCacheStrategy = DiskCacheStrategy.NONE + ) } else { thumbnail.gone() } @@ -163,7 +178,10 @@ class StreamsAdapter( tagsLayout.removeAllViews() tagsLayout.visible() val tagsFlowLayout = Flow(context).apply { - layoutParams = ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + layoutParams = ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topToTop = tagsLayout.id bottomToBottom = tagsLayout.id startToStart = tagsLayout.id @@ -184,15 +202,19 @@ class StreamsAdapter( } text.setOnClickListener { if (args?.gameId != null && args.gameName != null) { - fragment.findNavController().navigate(GamePagerFragmentDirections.actionGlobalGamePagerFragment( - gameId = args.gameId, - gameName = args.gameName, - tags = arrayOf(tag), - )) + fragment.findNavController().navigate( + GamePagerFragmentDirections.actionGlobalGamePagerFragment( + gameId = args.gameId, + gameName = args.gameName, + tags = arrayOf(tag), + ) + ) } else { - fragment.findNavController().navigate(TopFragmentDirections.actionGlobalTopFragment( - tags = arrayOf(tag) - )) + fragment.findNavController().navigate( + TopFragmentDirections.actionGlobalTopFragment( + tags = arrayOf(tag) + ) + ) } } val padding = context.convertDpToPixels(5f) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsCompactAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsCompactAdapter.kt index b70a0f098..2140c76a9 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsCompactAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/StreamsCompactAdapter.kt @@ -31,7 +31,8 @@ import com.github.andreyasadchy.xtra.util.visible class StreamsCompactAdapter( private val fragment: Fragment, private val args: GamePagerFragmentArgs? = null, - private val hideGame: Boolean = false) : PagingDataAdapter( + private val hideGame: Boolean = false, +) : PagingDataAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Stream, newItem: Stream): Boolean = oldItem.id == newItem.id @@ -55,18 +56,23 @@ class StreamsCompactAdapter( private val binding: FragmentStreamsListItemCompactBinding, private val fragment: Fragment, private val args: GamePagerFragmentArgs?, - private val hideGame: Boolean): RecyclerView.ViewHolder(binding.root) { + private val hideGame: Boolean, + ) : RecyclerView.ViewHolder(binding.root) { fun bind(item: Stream?) { with(binding) { if (item != null) { val context = fragment.requireContext() - val channelListener: (View) -> Unit = { fragment.findNavController().navigate(ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( - channelId = item.channelId, - channelLogin = item.channelLogin, - channelName = item.channelName, - channelLogo = item.channelLogo, - streamId = item.id - )) } + val channelListener: (View) -> Unit = { + fragment.findNavController().navigate( + ChannelPagerFragmentDirections.actionGlobalChannelPagerFragment( + channelId = item.channelId, + channelLogin = item.channelLogin, + channelName = item.channelName, + channelLogo = item.channelLogo, + streamId = item.id + ) + ) + } val gameListener: (View) -> Unit = { fragment.findNavController().navigate( if (context.prefs().getBoolean(C.UI_GAMEPAGER, true)) { @@ -87,14 +93,18 @@ class StreamsCompactAdapter( root.setOnClickListener { (fragment.activity as MainActivity).startStream(item) } - if (item.channelLogo != null) { + if (item.channelLogo != null) { userImage.visible() - userImage.loadImage(fragment, item.channelLogo, circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true)) + userImage.loadImage( + fragment, + item.channelLogo, + circle = context.prefs().getBoolean(C.UI_ROUNDUSERIMAGE, true) + ) userImage.setOnClickListener(channelListener) } else { userImage.gone() } - if (item.channelName != null) { + if (item.channelName != null) { username.visible() username.text = if (item.channelLogin != null && !item.channelLogin.equals(item.channelName, true)) { when (context.prefs().getString(C.UI_NAME_DISPLAY, "0")) { @@ -109,13 +119,13 @@ class StreamsCompactAdapter( } else { username.gone() } - if (item.title != null && item.title != "") { + if (item.title != null && item.title != "") { title.visible() title.text = item.title?.trim() } else { title.gone() } - if (!hideGame && item.gameName != null) { + if (!hideGame && item.gameName != null) { gameName.visible() gameName.text = item.gameName gameName.setOnClickListener(gameListener) @@ -154,7 +164,10 @@ class StreamsCompactAdapter( tagsLayout.removeAllViews() tagsLayout.visible() val tagsFlowLayout = Flow(context).apply { - layoutParams = ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { + layoutParams = ConstraintLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { topToTop = tagsLayout.id bottomToBottom = tagsLayout.id startToStart = tagsLayout.id @@ -174,11 +187,13 @@ class StreamsCompactAdapter( TextViewCompat.setTextAppearance(text, it.getResourceId(0, 0)) } text.setOnClickListener { - fragment.findNavController().navigate(GamePagerFragmentDirections.actionGlobalGamePagerFragment( - gameId = args?.gameId, - gameName = args?.gameName, - tags = arrayOf(tag), - )) + fragment.findNavController().navigate( + GamePagerFragmentDirections.actionGlobalGamePagerFragment( + gameId = args?.gameId, + gameName = args?.gameName, + tags = arrayOf(tag), + ) + ) } val padding = context.convertDpToPixels(5f) text.setPadding(padding, 0, padding, 0) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsSortDialog.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsSortDialog.kt index da5dd098d..50f96a010 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsSortDialog.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsSortDialog.kt @@ -74,11 +74,13 @@ class StreamsSortDialog : BottomSheetDialogFragment() { dismiss() } selectTags.setOnClickListener { - findNavController().navigate(TagSearchFragmentDirections.actionGlobalTagSearchFragment( - gameId = parentFragment?.arguments?.getString(C.GAME_ID), - gameSlug = parentFragment?.arguments?.getString(C.GAME_SLUG), - gameName = parentFragment?.arguments?.getString(C.GAME_NAME) - )) + findNavController().navigate( + TagSearchFragmentDirections.actionGlobalTagSearchFragment( + gameId = parentFragment?.arguments?.getString(C.GAME_ID), + gameSlug = parentFragment?.arguments?.getString(C.GAME_SLUG), + gameName = parentFragment?.arguments?.getString(C.GAME_NAME) + ) + ) dismiss() } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsViewModel.kt index 46750bd14..5eda0e81b 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/common/StreamsViewModel.kt @@ -30,7 +30,8 @@ class StreamsViewModel @Inject constructor( private val graphQLRepository: GraphQLRepository, private val helix: HelixApi, private val apolloClient: ApolloClient, - savedStateHandle: SavedStateHandle) : ViewModel() { + savedStateHandle: SavedStateHandle, +) : ViewModel() { private val args = GamePagerFragmentArgs.fromSavedStateHandle(savedStateHandle) val filter = MutableStateFlow(null) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/followed/FollowedStreamsViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/followed/FollowedStreamsViewModel.kt index d3d970e83..0af06f65a 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/followed/FollowedStreamsViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/streams/followed/FollowedStreamsViewModel.kt @@ -25,7 +25,8 @@ class FollowedStreamsViewModel @Inject constructor( private val graphQLRepository: GraphQLRepository, private val helix: HelixApi, private val apolloClient: ApolloClient, - private val localFollowsChannel: LocalFollowChannelRepository) : ViewModel() { + private val localFollowsChannel: LocalFollowChannelRepository, +) : ViewModel() { val flow = Pager( if (applicationContext.prefs().getString(C.COMPACT_STREAMS, "disabled") != "disabled") { diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/top/TopFragment.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/top/TopFragment.kt index ecc54f1d9..6c4df77f7 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/top/TopFragment.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/top/TopFragment.kt @@ -56,7 +56,8 @@ class TopFragment : Fragment(), Scrollable, FragmentHost { super.onViewCreated(view, savedInstanceState) with(binding) { val activity = requireActivity() as MainActivity - val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() + val isLoggedIn = !TwitchApiHelper.getGQLHeaders(requireContext(), true)[C.HEADER_TOKEN].isNullOrBlank() || + !TwitchApiHelper.getHelixHeaders(requireContext())[C.HEADER_TOKEN].isNullOrBlank() val navController = findNavController() val appBarConfiguration = AppBarConfiguration(setOf(R.id.rootGamesFragment, R.id.rootTopFragment, R.id.followPagerFragment, R.id.followMediaFragment, R.id.savedPagerFragment, R.id.savedMediaFragment)) toolbar.setupWithNavController(navController, appBarConfiguration) diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/BaseVideosViewModel.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/BaseVideosViewModel.kt index 458869a1a..4b2e2623e 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/BaseVideosViewModel.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/BaseVideosViewModel.kt @@ -19,7 +19,8 @@ abstract class BaseVideosViewModel( playerRepository: PlayerRepository, private val bookmarksRepository: BookmarksRepository, private val repository: ApiRepository, - private val okHttpClient: OkHttpClient) : ViewModel() { + private val okHttpClient: OkHttpClient, +) : ViewModel() { val positions = playerRepository.loadVideoPositions() val bookmarks = bookmarksRepository.loadBookmarksFlow() @@ -75,24 +76,26 @@ abstract class BaseVideosViewModel( } catch (e: Exception) { null } - bookmarksRepository.saveBookmark(Bookmark( - videoId = video.id, - userId = video.channelId, - userLogin = video.channelLogin, - userName = video.channelName, - userType = userTypes?.type, - userBroadcasterType = userTypes?.broadcasterType, - userLogo = downloadedLogo, - gameId = video.gameId, - gameSlug = video.gameSlug, - gameName = video.gameName, - title = video.title, - createdAt = video.uploadDate, - thumbnail = downloadedThumbnail, - type = video.type, - duration = video.duration, - animatedPreviewURL = video.animatedPreviewURL - )) + bookmarksRepository.saveBookmark( + Bookmark( + videoId = video.id, + userId = video.channelId, + userLogin = video.channelLogin, + userName = video.channelName, + userType = userTypes?.type, + userBroadcasterType = userTypes?.broadcasterType, + userLogo = downloadedLogo, + gameId = video.gameId, + gameSlug = video.gameSlug, + gameName = video.gameName, + title = video.title, + createdAt = video.uploadDate, + thumbnail = downloadedThumbnail, + type = video.type, + duration = video.duration, + animatedPreviewURL = video.animatedPreviewURL + ) + ) } } } diff --git a/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/VideosAdapter.kt b/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/VideosAdapter.kt index 112a88539..8eabb37db 100644 --- a/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/VideosAdapter.kt +++ b/app/src/main/java/com/github/andreyasadchy/xtra/ui/videos/VideosAdapter.kt @@ -35,7 +35,8 @@ class VideosAdapter( private val fragment: Fragment, private val showDownloadDialog: (Video) -> Unit, private val saveBookmark: (Video) -> Unit, - private val hideGame: Boolean = false) : BaseVideosAdapter( + private val hideGame: Boolean = false, +) : BaseVideosAdapter( object : DiffUtil.ItemCallback diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 61aebbde3..63f4c62a5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -41,6 +41,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" - android:focusable="true"/> + android:focusable="true" /> diff --git a/app/src/main/res/layout/auto_complete_emotes_list_item.xml b/app/src/main/res/layout/auto_complete_emotes_list_item.xml index f2bb533a5..a648de3f4 100644 --- a/app/src/main/res/layout/auto_complete_emotes_list_item.xml +++ b/app/src/main/res/layout/auto_complete_emotes_list_item.xml @@ -15,7 +15,7 @@ android:id="@+id/image" android:layout_width="32dp" android:layout_height="32dp" - tools:src="@tools:sample/avatars"/> + tools:src="@tools:sample/avatars" /> + tools:visibility="visible" /> + app:layout_constraintRight_toRightOf="parent" /> + android:text="@string/copy_clip" />