Add apps for DevBytes, MarsRealEstate
54
DevBytesRepository/README.md
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
DevByteRepository - Solution Code
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Solution code for the Repository codelab.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
DevByteRepository app displays a list of DevByte videos. DevByte videos are
|
||||||
|
short videos made by the Google Android developer relations team to introduce
|
||||||
|
new developer features on Android. This app demonstrates the Repository pattern,
|
||||||
|
the recommended best practice for code separation and architecture. Using
|
||||||
|
repository pattern the data layer is abstracted from the rest of the app.
|
||||||
|
Repositories act as mediators between different data sources, such as persistent
|
||||||
|
models, web services, and caches and the rest of the app.
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You need to know:
|
||||||
|
- How to open, build, and run Android apps with Android Studio.
|
||||||
|
- The basic Android Architecture Components, ViewModel, and LiveData.
|
||||||
|
- The data persistence library, Room.
|
||||||
|
- Building and launching a coroutine.
|
||||||
|
- Read the logs using the Logcat.
|
||||||
|
- Binding adapters in data binding.
|
||||||
|
- Using the Retrofit networking library.
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
1. Download and run the app.
|
||||||
|
2. You need Android Studio 3.4 or higher to build this project.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2019 Google, Inc.
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||||
|
license agreements. See the NOTICE file distributed with this work for
|
||||||
|
additional information regarding copyright ownership. The ASF licenses this
|
||||||
|
file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License.
|
||||||
103
DevBytesRepository/app/build.gradle
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: "androidx.navigation.safeargs"
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.example.android.devbyteviewer"
|
||||||
|
minSdkVersion 19
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
// support libraries
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
|
||||||
|
// Android KTX
|
||||||
|
implementation 'androidx.core:core-ktx:1.0.2'
|
||||||
|
|
||||||
|
// constraint layout
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
|
||||||
|
// navigation
|
||||||
|
def nav_version = "1.0.0"
|
||||||
|
implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version"
|
||||||
|
implementation "android.arch.navigation:navigation-ui-ktx:$nav_version"
|
||||||
|
|
||||||
|
// coroutines for getting off the UI thread
|
||||||
|
def coroutines = "1.0.1"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
|
// retrofit for networking
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||||
|
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
|
||||||
|
implementation 'com.squareup.retrofit2:converter-moshi:2.5.0'
|
||||||
|
|
||||||
|
// moshi for parsing the JSON format
|
||||||
|
def moshi_version = "1.6.0"
|
||||||
|
implementation "com.squareup.moshi:moshi:$moshi_version"
|
||||||
|
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
|
||||||
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||||
|
|
||||||
|
// joda time library for dealing with time
|
||||||
|
implementation 'joda-time:joda-time:2.10'
|
||||||
|
|
||||||
|
// arch components
|
||||||
|
// ViewModel and LiveData
|
||||||
|
def lifecycle_version = "2.2.0-alpha01"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
|
|
||||||
|
// logging
|
||||||
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
|
||||||
|
// glide for images
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.8.0'
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.7.1'
|
||||||
|
|
||||||
|
// Room dependency
|
||||||
|
def room_version = "2.1.0-beta01"
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
|
}
|
||||||
21
DevBytesRepository/app/proguard-rules.pro
vendored
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
42
DevBytesRepository/app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.example.android.devbyteviewer">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".DevByteApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
<activity android:name=".ui.DevByteActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override application to setup background work via WorkManager
|
||||||
|
*/
|
||||||
|
class DevByteApplication : Application() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onCreate is called before the first screen is shown to the user.
|
||||||
|
*
|
||||||
|
* Use it to setup any background tasks, running expensive setup operations in a background
|
||||||
|
* thread to avoid delaying app start.
|
||||||
|
*/
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.database
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database entities go in this file. These are responsible for reading and writing from the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DatabaseVideo represents a video entity in the database.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
data class DatabaseVideo constructor(
|
||||||
|
@PrimaryKey
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val thumbnail: String)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map DatabaseVideos to domain entities
|
||||||
|
*/
|
||||||
|
fun List<DatabaseVideo>.asDomainModel(): List<DevByteVideo> {
|
||||||
|
return map {
|
||||||
|
DevByteVideo(
|
||||||
|
url = it.url,
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface VideoDao {
|
||||||
|
@Query("select * from databasevideo")
|
||||||
|
fun getVideos(): LiveData<List<DatabaseVideo>>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAll( videos: List<DatabaseVideo>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Database(entities = [DatabaseVideo::class], version = 1)
|
||||||
|
abstract class VideosDatabase: RoomDatabase() {
|
||||||
|
abstract val videoDao: VideoDao
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var INSTANCE: VideosDatabase
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): VideosDatabase {
|
||||||
|
synchronized(VideosDatabase::class.java) {
|
||||||
|
if (!::INSTANCE.isInitialized) {
|
||||||
|
INSTANCE = Room.databaseBuilder(context.applicationContext,
|
||||||
|
VideosDatabase::class.java,
|
||||||
|
"videos").build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INSTANCE
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.domain
|
||||||
|
|
||||||
|
import com.example.android.devbyteviewer.util.smartTruncate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain objects are plain Kotlin data classes that represent the things in our app. These are the
|
||||||
|
* objects that should be displayed on screen, or manipulated by the app.
|
||||||
|
*
|
||||||
|
* @see database for objects that are mapped to the database
|
||||||
|
* @see network for objects that parse or prepare network calls
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Videos represent a devbyte that can be played.
|
||||||
|
*/
|
||||||
|
data class DevByteVideo(val title: String,
|
||||||
|
val description: String,
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val thumbnail: String) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short description is used for displaying truncated descriptions in the UI
|
||||||
|
*/
|
||||||
|
val shortDescription: String
|
||||||
|
get() = description.smartTruncate(200)
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.network
|
||||||
|
|
||||||
|
import com.example.android.devbyteviewer.database.DatabaseVideo
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataTransferObjects go in this file. These are responsible for parsing responses from the server
|
||||||
|
* or formatting objects to send to the server. You should convert these to domain objects before
|
||||||
|
* using them.
|
||||||
|
*
|
||||||
|
* @see domain package for
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VideoHolder holds a list of Videos.
|
||||||
|
*
|
||||||
|
* This is to parse first level of our network result which looks like
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "videos": []
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class NetworkVideoContainer(val videos: List<NetworkVideo>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Videos represent a devbyte that can be played.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class NetworkVideo(
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val closedCaptions: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Network results to database objects
|
||||||
|
*/
|
||||||
|
fun NetworkVideoContainer.asDomainModel(): List<DevByteVideo> {
|
||||||
|
return videos.map {
|
||||||
|
DevByteVideo(
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
url = it.url,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Network results to database objects
|
||||||
|
*/
|
||||||
|
fun NetworkVideoContainer.asDatabaseModel(): List<DatabaseVideo> {
|
||||||
|
return videos.map {
|
||||||
|
DatabaseVideo(
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
url = it.url,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.network
|
||||||
|
|
||||||
|
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
// Since we only have one service, this can all go in one file.
|
||||||
|
// If you add more services, split this to multiple files and make sure to share the retrofit
|
||||||
|
// object between services.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A retrofit service to fetch a devbyte playlist.
|
||||||
|
*/
|
||||||
|
interface DevbyteService {
|
||||||
|
@GET("devbytes")
|
||||||
|
fun getPlaylist(): Deferred<NetworkVideoContainer>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for network access. Call like `DevByteNetwork.devbytes.getPlaylist()`
|
||||||
|
*/
|
||||||
|
object DevByteNetwork {
|
||||||
|
|
||||||
|
// Configure retrofit to parse JSON and use coroutines
|
||||||
|
private val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://android-kotlin-fun-mars-server.appspot.com/")
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
|
.addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val devbytes = retrofit.create(DevbyteService::class.java)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.repository
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.example.android.devbyteviewer.database.VideosDatabase
|
||||||
|
import com.example.android.devbyteviewer.database.asDomainModel
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.network.DevByteNetwork
|
||||||
|
import com.example.android.devbyteviewer.network.asDatabaseModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository for fetching devbyte videos from the network and storing them on disk
|
||||||
|
*/
|
||||||
|
class VideosRepository(private val database: VideosDatabase) {
|
||||||
|
|
||||||
|
val videos: LiveData<List<DevByteVideo>> = Transformations.map(database.videoDao.getVideos()) {
|
||||||
|
it.asDomainModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the videos stored in the offline cache.
|
||||||
|
*
|
||||||
|
* This function uses the IO dispatcher to ensure the database insert database operation
|
||||||
|
* happens on the IO dispatcher. By switching to the IO dispatcher using `withContext` this
|
||||||
|
* function is now safe to call from any thread including the Main thread.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
suspend fun refreshVideos() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Timber.d("refresh videos is called");
|
||||||
|
val playlist = DevByteNetwork.devbytes.getPlaylist().await()
|
||||||
|
database.videoDao.insertAll(playlist.asDatabaseModel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.example.android.devbyteviewer.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a single activity application that uses the Navigation library. Content is displayed
|
||||||
|
* by Fragments.
|
||||||
|
*/
|
||||||
|
class DevByteActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is starting. This is where most initialization
|
||||||
|
* should go
|
||||||
|
*/
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_dev_byte_viewer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.example.android.devbyteviewer.R
|
||||||
|
import com.example.android.devbyteviewer.databinding.DevbyteItemBinding
|
||||||
|
import com.example.android.devbyteviewer.databinding.FragmentDevByteBinding
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.viewmodels.DevByteViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a list of DevBytes on screen.
|
||||||
|
*/
|
||||||
|
class DevByteFragment : Fragment() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One way to delay creation of the viewModel until an appropriate lifecycle method is to use
|
||||||
|
* lazy. This requires that viewModel not be referenced before onActivityCreated, which we
|
||||||
|
* do in this Fragment.
|
||||||
|
*/
|
||||||
|
private val viewModel: DevByteViewModel by lazy {
|
||||||
|
val activity = requireNotNull(this.activity) {
|
||||||
|
"You can only access the viewModel after onActivityCreated()"
|
||||||
|
}
|
||||||
|
ViewModelProviders.of(this, DevByteViewModel.Factory(activity.application))
|
||||||
|
.get(DevByteViewModel::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView Adapter for converting a list of Video to cards.
|
||||||
|
*/
|
||||||
|
private var viewModelAdapter: DevByteAdapter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the fragment's activity has been created and this
|
||||||
|
* fragment's view hierarchy instantiated. It can be used to do final
|
||||||
|
* initialization once these pieces are in place, such as retrieving
|
||||||
|
* views or restoring state.
|
||||||
|
*/
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
viewModel.playlist.observe(viewLifecycleOwner, Observer<List<DevByteVideo>> { videos ->
|
||||||
|
videos?.apply {
|
||||||
|
viewModelAdapter?.videos = videos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to have the fragment instantiate its user interface view.
|
||||||
|
*
|
||||||
|
* <p>If you return a View from here, you will later be called in
|
||||||
|
* {@link #onDestroyView} when the view is being released.
|
||||||
|
*
|
||||||
|
* @param inflater The LayoutInflater object that can be used to inflate
|
||||||
|
* any views in the fragment,
|
||||||
|
* @param container If non-null, this is the parent view that the fragment's
|
||||||
|
* UI should be attached to. The fragment should not add the view itself,
|
||||||
|
* but this can be used to generate the LayoutParams of the view.
|
||||||
|
* @param savedInstanceState If non-null, this fragment is being re-constructed
|
||||||
|
* from a previous saved state as given here.
|
||||||
|
*
|
||||||
|
* @return Return the View for the fragment's UI.
|
||||||
|
*/
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
val binding: FragmentDevByteBinding = DataBindingUtil.inflate(
|
||||||
|
inflater,
|
||||||
|
R.layout.fragment_dev_byte,
|
||||||
|
container,
|
||||||
|
false)
|
||||||
|
// Set the lifecycleOwner so DataBinding can observe LiveData
|
||||||
|
binding.setLifecycleOwner(viewLifecycleOwner)
|
||||||
|
|
||||||
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
|
viewModelAdapter = DevByteAdapter(VideoClick {
|
||||||
|
// When a video is clicked this block or lambda will be called by DevByteAdapter
|
||||||
|
|
||||||
|
// context is not around, we can safely discard this click since the Fragment is no
|
||||||
|
// longer on the screen
|
||||||
|
val packageManager = context?.packageManager ?: return@VideoClick
|
||||||
|
|
||||||
|
// Try to generate a direct intent to the YouTube app
|
||||||
|
var intent = Intent(Intent.ACTION_VIEW, it.launchUri)
|
||||||
|
if(intent.resolveActivity(packageManager) == null) {
|
||||||
|
// YouTube app isn't found, use the web url
|
||||||
|
intent = Intent(Intent.ACTION_VIEW, Uri.parse(it.url))
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.root.findViewById<RecyclerView>(R.id.recycler_view).apply {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = viewModelAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Observer for the network error.
|
||||||
|
viewModel.eventNetworkError.observe(this, Observer<Boolean> { isNetworkError ->
|
||||||
|
if (isNetworkError) onNetworkError()
|
||||||
|
})
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for displaying a Toast error message for network errors.
|
||||||
|
*/
|
||||||
|
private fun onNetworkError() {
|
||||||
|
if(!viewModel.isNetworkErrorShown.value!!) {
|
||||||
|
Toast.makeText(activity, "Network Error", Toast.LENGTH_LONG).show()
|
||||||
|
viewModel.onNetworkErrorShown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to generate YouTube app links
|
||||||
|
*/
|
||||||
|
private val DevByteVideo.launchUri: Uri
|
||||||
|
get() {
|
||||||
|
val httpUri = Uri.parse(url)
|
||||||
|
return Uri.parse("vnd.youtube:" + httpUri.getQueryParameter("v"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click listener for Videos. By giving the block a name it helps a reader understand what it does.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class VideoClick(val block: (DevByteVideo) -> Unit) {
|
||||||
|
/**
|
||||||
|
* Called when a video is clicked
|
||||||
|
*
|
||||||
|
* @param video the video that was clicked
|
||||||
|
*/
|
||||||
|
fun onClick(video: DevByteVideo) = block(video)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView Adapter for setting up data binding on the items in the list.
|
||||||
|
*/
|
||||||
|
class DevByteAdapter(val callback: VideoClick) : RecyclerView.Adapter<DevByteViewHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The videos that our Adapter will show
|
||||||
|
*/
|
||||||
|
var videos: List<DevByteVideo> = emptyList()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
// For an extra challenge, update this to use the paging library.
|
||||||
|
|
||||||
|
// Notify any registered observers that the data set has changed. This will cause every
|
||||||
|
// element in our RecyclerView to be invalidated.
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
|
||||||
|
* an item.
|
||||||
|
*/
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DevByteViewHolder {
|
||||||
|
val withDataBinding: DevbyteItemBinding = DataBindingUtil.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
DevByteViewHolder.LAYOUT,
|
||||||
|
parent,
|
||||||
|
false)
|
||||||
|
return DevByteViewHolder(withDataBinding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = videos.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by RecyclerView to display the data at the specified position. This method should
|
||||||
|
* update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
|
||||||
|
* position.
|
||||||
|
*/
|
||||||
|
override fun onBindViewHolder(holder: DevByteViewHolder, position: Int) {
|
||||||
|
holder.viewDataBinding.also {
|
||||||
|
it.video = videos[position]
|
||||||
|
it.videoCallback = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for DevByte items. All work is done by data binding.
|
||||||
|
*/
|
||||||
|
class DevByteViewHolder(val viewDataBinding: DevbyteItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(viewDataBinding.root) {
|
||||||
|
companion object {
|
||||||
|
@LayoutRes
|
||||||
|
val LAYOUT = R.layout.devbyte_item
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.util
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binding adapter used to hide the spinner once data is available.
|
||||||
|
*/
|
||||||
|
@BindingAdapter("isNetworkError", "playlist")
|
||||||
|
fun hideIfNetworkError(view: View, isNetWorkError: Boolean, playlist: Any?) {
|
||||||
|
view.visibility = if (playlist != null) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
if(isNetWorkError) {
|
||||||
|
view.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binding adapter used to display images from URL using Glide
|
||||||
|
*/
|
||||||
|
@BindingAdapter("imageUrl")
|
||||||
|
fun setImageUrl(imageView: ImageView, url: String) {
|
||||||
|
Glide.with(imageView.context).load(url).into(imageView)
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.util
|
||||||
|
|
||||||
|
private val PUNCTUATION = listOf(", ", "; ", ": ", " ")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate long text with a preference for word boundaries and without trailing punctuation.
|
||||||
|
*/
|
||||||
|
fun String.smartTruncate(length: Int): String {
|
||||||
|
val words = split(" ")
|
||||||
|
var added = 0
|
||||||
|
var hasMore = false
|
||||||
|
val builder = StringBuilder()
|
||||||
|
for (word in words) {
|
||||||
|
if (builder.length > length) {
|
||||||
|
hasMore = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
builder.append(word)
|
||||||
|
builder.append(" ")
|
||||||
|
added += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
PUNCTUATION.map {
|
||||||
|
if (builder.endsWith(it)) {
|
||||||
|
builder.replace(builder.length - it.length, builder.length, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
builder.append("...")
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.example.android.devbyteviewer.database.getDatabase
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.network.DevByteNetwork
|
||||||
|
import com.example.android.devbyteviewer.network.asDomainModel
|
||||||
|
import com.example.android.devbyteviewer.repository.VideosRepository
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DevByteViewModel designed to store and manage UI-related data in a lifecycle conscious way. This
|
||||||
|
* allows data to survive configuration changes such as screen rotations. In addition, background
|
||||||
|
* work such as fetching network results can continue through configuration changes and deliver
|
||||||
|
* results after the new Fragment or Activity is available.
|
||||||
|
*
|
||||||
|
* @param application The application that this viewmodel is attached to, it's safe to hold a
|
||||||
|
* reference to applications across rotation since Application is never recreated during actiivty
|
||||||
|
* or fragment lifecycle events.
|
||||||
|
*/
|
||||||
|
class DevByteViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data source this ViewModel will fetch results from.
|
||||||
|
*/
|
||||||
|
private val videosRepository = VideosRepository(getDatabase(application))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A playlist of videos displayed on the screen.
|
||||||
|
*/
|
||||||
|
val playlist = videosRepository.videos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the job for all coroutines started by this ViewModel.
|
||||||
|
*
|
||||||
|
* Cancelling this job will cancel all coroutines started by this ViewModel.
|
||||||
|
*/
|
||||||
|
private val viewModelJob = SupervisorJob()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main scope for all coroutines launched by MainViewModel.
|
||||||
|
*
|
||||||
|
* Since we pass viewModelJob, you can cancel all coroutines launched by uiScope by calling
|
||||||
|
* viewModelJob.cancel()
|
||||||
|
*/
|
||||||
|
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered for network error. This is private to avoid exposing a
|
||||||
|
* way to set this value to observers.
|
||||||
|
*/
|
||||||
|
private var _eventNetworkError = MutableLiveData<Boolean>(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered for network error. Views should use this to get access
|
||||||
|
* to the data.
|
||||||
|
*/
|
||||||
|
val eventNetworkError: LiveData<Boolean>
|
||||||
|
get() = _eventNetworkError
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to display the error message. This is private to avoid exposing a
|
||||||
|
* way to set this value to observers.
|
||||||
|
*/
|
||||||
|
private var _isNetworkErrorShown = MutableLiveData<Boolean>(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to display the error message. Views should use this to get access
|
||||||
|
* to the data.
|
||||||
|
*/
|
||||||
|
val isNetworkErrorShown: LiveData<Boolean>
|
||||||
|
get() = _isNetworkErrorShown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init{} is called immediately when this ViewModel is created.
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
refreshDataFromRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh data from the repository. Use a coroutine launch to run in a
|
||||||
|
* background thread.
|
||||||
|
*/
|
||||||
|
private fun refreshDataFromRepository() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
videosRepository.refreshVideos()
|
||||||
|
_eventNetworkError.value = false
|
||||||
|
_isNetworkErrorShown.value = false
|
||||||
|
|
||||||
|
} catch (networkError: IOException) {
|
||||||
|
// Show a Toast error message and hide the progress bar.
|
||||||
|
if(playlist.value!!.isEmpty())
|
||||||
|
_eventNetworkError.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the network error flag.
|
||||||
|
*/
|
||||||
|
fun onNetworkErrorShown() {
|
||||||
|
_isNetworkErrorShown.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel all coroutines when the ViewModel is cleared
|
||||||
|
*/
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
viewModelJob.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for constructing DevByteViewModel with parameter
|
||||||
|
*/
|
||||||
|
class Factory(val app: Application) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(DevByteViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return DevByteViewModel(app) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unable to construct viewmodel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
DevBytesRepository/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
186
DevBytesRepository/app/src/main/res/drawable/ic_launcher_background.xml
Executable file
@ -0,0 +1,186 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#008577"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="48"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M20,33l12,-9 -12,-9v18zM24,4C12.95,4 4,12.95 4,24s8.95,20 20,20 20,-8.95 20,-20S35.05,4 24,4zM24,40c-8.82,0 -16,-7.18 -16,-16S15.18,8 24,8s16,7.18 16,16 -7.18,16 -16,16z" />
|
||||||
|
</vector>
|
||||||
33
DevBytesRepository/app/src/main/res/layout/activity_dev_byte_viewer.xml
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.DevByteActivity">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/my_nav_host_fragment"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
app:navGraph="@navigation/nav_graph"
|
||||||
|
app:defaultNavHost="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
130
DevBytesRepository/app/src/main/res/layout/devbyte_item.xml
Executable file
@ -0,0 +1,130 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="video"
|
||||||
|
type="com.example.android.devbyteviewer.domain.DevByteVideo" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="videoCallback"
|
||||||
|
type="com.example.android.devbyteviewer.ui.VideoClick" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="0dp"
|
||||||
|
app:cardElevation="5dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/left_well"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_begin="8dp" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/right_well"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_end="8dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/play_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/left_well"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_thumbnail"
|
||||||
|
app:srcCompat="@drawable/ic_play_circle_outline_black_48dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@{video.title}"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/text_black"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/right_well"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/play_icon"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_thumbnail"
|
||||||
|
tools:text="Video title" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@{video.shortDescription}"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/right_well"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/left_well"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:text="this is a video @android:string/fingerprint_icon_content_description" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/video_thumbnail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:adjustViewBounds="false"
|
||||||
|
android:cropToPadding="false"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:imageUrl="@{video.thumbnail}"
|
||||||
|
app:layout_constraintDimensionRatio="h,4:3"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/clickableOverlay"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:onClick="@{() -> videoCallback.onClick(video)}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</layout>
|
||||||
53
DevBytesRepository/app/src/main/res/layout/fragment_dev_byte.xml
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".ui.DevByteFragment">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.example.android.devbyteviewer.viewmodels.DevByteViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading_spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:isNetworkError = "@{safeUnbox(viewModel.eventNetworkError)}"
|
||||||
|
app:playlist = "@{viewModel.playlist}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/devbyte_item" />
|
||||||
|
</FrameLayout>
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
|
||||||
21
DevBytesRepository/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
21
DevBytesRepository/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
BIN
DevBytesRepository/app/src/main/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-mdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
DevBytesRepository/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
29
DevBytesRepository/app/src/main/res/navigation/nav_graph.xml
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/nav_graph"
|
||||||
|
app:startDestination="@id/devByte">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/devByte"
|
||||||
|
android:name="com.example.android.devbyteviewer.ui.DevByteFragment"
|
||||||
|
android:label="fragment_dev_byte"
|
||||||
|
tools:layout="@layout/fragment_dev_byte" />
|
||||||
|
</navigation>
|
||||||
26
DevBytesRepository/app/src/main/res/values/colors.xml
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#1E8E3E</color>
|
||||||
|
<color name="colorPrimaryDark">#0D652D</color>
|
||||||
|
<color name="colorAccent">#E37400</color>
|
||||||
|
|
||||||
|
<color name="background_grey">#ffDDDDDD</color>
|
||||||
|
<color name="text_light">#ff666666</color>
|
||||||
|
<color name="text_black">#ff222222</color>
|
||||||
|
</resources>
|
||||||
19
DevBytesRepository/app/src/main/res/values/strings.xml
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">DevByte Viewer</string>
|
||||||
|
</resources>
|
||||||
27
DevBytesRepository/app/src/main/res/values/styles.xml
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
45
DevBytesRepository/build.gradle
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.31'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
maven { url "https://kotlin.bintray.com/kotlinx/" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
31
DevBytesRepository/gradle.properties
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2019 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
BIN
DevBytesRepository/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
6
DevBytesRepository/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#Thu Apr 25 13:35:31 PDT 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||||
172
DevBytesRepository/gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
DevBytesRepository/gradlew.bat
vendored
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
17
DevBytesRepository/settings.gradle
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
include ':app'
|
||||||
56
DevBytesWorkManager/README.md
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
DevByteWorkManager - Solution Code
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Solution code for the WorkManager codelab.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
DevByteWorkManager app displays a list of DevByte videos. DevByte videos are
|
||||||
|
short videos made by the Google Android developer relations team to introduce
|
||||||
|
new developer features on Android. This app fetches the DevByte video playlist
|
||||||
|
from the network using the Retrofit library and displays it on the screen. The
|
||||||
|
network fetch is scheduled periodically once a day using the WorkManager.
|
||||||
|
Constraints like device idle, unmettered network and so on are added to the work
|
||||||
|
request to optimise the battery performance.
|
||||||
|
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You need to know:
|
||||||
|
- How to open, build, and run Android apps with Android Studio.
|
||||||
|
- The Android Architecture Components like, ViewModel, LiveData, and Room.
|
||||||
|
- Building and launching a coroutine.
|
||||||
|
- Read the logs using the Logcat.
|
||||||
|
- Binding adapters in Data Binding.
|
||||||
|
- How to use Retrofit network library.
|
||||||
|
- Loading cached data using a Repository pattern.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
1. Download and run the app.
|
||||||
|
2. You need Android Studio 3.2 or higher to build this project.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2019 Google, Inc.
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||||
|
license agreements. See the NOTICE file distributed with this work for
|
||||||
|
additional information regarding copyright ownership. The ASF licenses this
|
||||||
|
file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License.
|
||||||
106
DevBytesWorkManager/app/build.gradle
Executable file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: "androidx.navigation.safeargs"
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.example.android.devbyteviewer"
|
||||||
|
minSdkVersion 19
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
// support libraries
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
|
||||||
|
// Android KTX
|
||||||
|
implementation 'androidx.core:core-ktx:1.0.2'
|
||||||
|
|
||||||
|
// constraint layout
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
|
||||||
|
// navigation
|
||||||
|
def nav_version = "1.0.0"
|
||||||
|
implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version"
|
||||||
|
implementation "android.arch.navigation:navigation-ui-ktx:$nav_version"
|
||||||
|
|
||||||
|
// coroutines for getting off the UI thread
|
||||||
|
def coroutines = "1.0.1"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
|
||||||
|
|
||||||
|
// retrofit for networking
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||||
|
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
|
||||||
|
implementation 'com.squareup.retrofit2:converter-moshi:2.5.0'
|
||||||
|
|
||||||
|
// moshi for parsing the JSON format
|
||||||
|
def moshi_version = "1.6.0"
|
||||||
|
implementation "com.squareup.moshi:moshi:$moshi_version"
|
||||||
|
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
|
||||||
|
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
|
||||||
|
|
||||||
|
// joda time library for dealing with time
|
||||||
|
implementation 'joda-time:joda-time:2.10'
|
||||||
|
|
||||||
|
// arch components
|
||||||
|
// ViewModel and LiveData
|
||||||
|
def lifecycle_version = "2.2.0-alpha01"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
|
|
||||||
|
// logging
|
||||||
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
|
||||||
|
// glide for images
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.8.0'
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.7.1'
|
||||||
|
|
||||||
|
// Room dependency
|
||||||
|
def room_version = "2.1.0-beta01"
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
|
// WorkManager dependency
|
||||||
|
def work_version = "1.0.1"
|
||||||
|
implementation "android.arch.work:work-runtime-ktx:$work_version"
|
||||||
|
}
|
||||||
21
DevBytesWorkManager/app/proguard-rules.pro
vendored
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
42
DevBytesWorkManager/app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.example.android.devbyteviewer">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".DevByteApplication"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
<activity android:name=".ui.DevByteActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.work.*
|
||||||
|
|
||||||
|
import com.example.android.devbyteviewer.work.RefreshDataWorker
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override application to setup background work via WorkManager
|
||||||
|
*/
|
||||||
|
class DevByteApplication : Application() {
|
||||||
|
|
||||||
|
private val applicationScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* onCreate is called before the first screen is shown to the user.
|
||||||
|
*
|
||||||
|
* Use it to setup any background tasks, running expensive setup operations in a background
|
||||||
|
* thread to avoid delaying app start.
|
||||||
|
*/
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
delayedInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delayedInit() {
|
||||||
|
applicationScope.launch {
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
setupRecurringWork()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup WorkManager background job to 'fetch' new network data daily.
|
||||||
|
*/
|
||||||
|
private fun setupRecurringWork() {
|
||||||
|
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||||
|
.setRequiresCharging(true)
|
||||||
|
.setRequiresBatteryNotLow(true)
|
||||||
|
.apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
setRequiresDeviceIdle(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Timber.d("WorkManager: Periodic Work request for sync is scheduled")
|
||||||
|
WorkManager.getInstance().enqueueUniquePeriodicWork(
|
||||||
|
RefreshDataWorker.WORK_NAME,
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
repeatingRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.database
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database entities go in this file. These are responsible for reading and writing from the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DatabaseVideo represents a video entity in the database.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
data class DatabaseVideo constructor(
|
||||||
|
@PrimaryKey
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val thumbnail: String)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map DatabaseVideos to domain entities
|
||||||
|
*/
|
||||||
|
fun List<DatabaseVideo>.asDomainModel(): List<DevByteVideo> {
|
||||||
|
return map {
|
||||||
|
DevByteVideo(
|
||||||
|
url = it.url,
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.*
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface VideoDao {
|
||||||
|
@Query("select * from databasevideo")
|
||||||
|
fun getVideos(): LiveData<List<DatabaseVideo>>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAll( videos: List<DatabaseVideo>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Database(entities = [DatabaseVideo::class], version = 1)
|
||||||
|
abstract class VideosDatabase: RoomDatabase() {
|
||||||
|
abstract val videoDao: VideoDao
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var INSTANCE: VideosDatabase
|
||||||
|
|
||||||
|
fun getDatabase(context: Context): VideosDatabase {
|
||||||
|
synchronized(VideosDatabase::class.java) {
|
||||||
|
if (!::INSTANCE.isInitialized) {
|
||||||
|
INSTANCE = Room.databaseBuilder(context.applicationContext,
|
||||||
|
VideosDatabase::class.java,
|
||||||
|
"videos").build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INSTANCE
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.domain
|
||||||
|
|
||||||
|
import com.example.android.devbyteviewer.util.smartTruncate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain objects are plain Kotlin data classes that represent the things in our app. These are the
|
||||||
|
* objects that should be displayed on screen, or manipulated by the app.
|
||||||
|
*
|
||||||
|
* @see database for objects that are mapped to the database
|
||||||
|
* @see network for objects that parse or prepare network calls
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Videos represent a devbyte that can be played.
|
||||||
|
*/
|
||||||
|
data class DevByteVideo(val title: String,
|
||||||
|
val description: String,
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val thumbnail: String) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short description is used for displaying truncated descriptions in the UI
|
||||||
|
*/
|
||||||
|
val shortDescription: String
|
||||||
|
get() = description.smartTruncate(200)
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.network
|
||||||
|
|
||||||
|
import com.example.android.devbyteviewer.database.DatabaseVideo
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataTransferObjects go in this file. These are responsible for parsing responses from the server
|
||||||
|
* or formatting objects to send to the server. You should convert these to domain objects before
|
||||||
|
* using them.
|
||||||
|
*
|
||||||
|
* @see domain package for
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VideoHolder holds a list of Videos.
|
||||||
|
*
|
||||||
|
* This is to parse first level of our network result which looks like
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "videos": []
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class NetworkVideoContainer(val videos: List<NetworkVideo>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Videos represent a devbyte that can be played.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class NetworkVideo(
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val url: String,
|
||||||
|
val updated: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val closedCaptions: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Network results to database objects
|
||||||
|
*/
|
||||||
|
fun NetworkVideoContainer.asDomainModel(): List<DevByteVideo> {
|
||||||
|
return videos.map {
|
||||||
|
DevByteVideo(
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
url = it.url,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Network results to database objects
|
||||||
|
*/
|
||||||
|
fun NetworkVideoContainer.asDatabaseModel(): List<DatabaseVideo> {
|
||||||
|
return videos.map {
|
||||||
|
DatabaseVideo(
|
||||||
|
title = it.title,
|
||||||
|
description = it.description,
|
||||||
|
url = it.url,
|
||||||
|
updated = it.updated,
|
||||||
|
thumbnail = it.thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.network
|
||||||
|
|
||||||
|
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
// Since we only have one service, this can all go in one file.
|
||||||
|
// If you add more services, split this to multiple files and make sure to share the retrofit
|
||||||
|
// object between services.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A retrofit service to fetch devbyte playlist.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface DevbyteService {
|
||||||
|
@GET("devbytes")
|
||||||
|
fun getPlaylist(): Deferred<NetworkVideoContainer>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for network access. Call like `DevByteNetwork.devbytes.getPlaylist()`
|
||||||
|
*/
|
||||||
|
object DevByteNetwork {
|
||||||
|
|
||||||
|
// Configure retrofit to parse JSON and use coroutines
|
||||||
|
private val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl("https://android-kotlin-fun-mars-server.appspot.com/")
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
|
.addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val devbytes = retrofit.create(DevbyteService::class.java)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.repository
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.example.android.devbyteviewer.database.VideosDatabase
|
||||||
|
import com.example.android.devbyteviewer.database.asDomainModel
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.network.DevByteNetwork
|
||||||
|
import com.example.android.devbyteviewer.network.asDatabaseModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository for fetching devbyte videos from the network and storing them on disk
|
||||||
|
*/
|
||||||
|
class VideosRepository(private val database: VideosDatabase) {
|
||||||
|
|
||||||
|
val videos: LiveData<List<DevByteVideo>> = Transformations.map(database.videoDao.getVideos()) {
|
||||||
|
it.asDomainModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the videos stored in the offline cache.
|
||||||
|
*
|
||||||
|
* This function uses the IO dispatcher to ensure the database insert database operation
|
||||||
|
* happens on the IO dispatcher. By switching to the IO dispatcher using `withContext` this
|
||||||
|
* function is now safe to call from any thread including the Main thread.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
suspend fun refreshVideos() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Timber.d("refresh videos is called");
|
||||||
|
val playlist = DevByteNetwork.devbytes.getPlaylist().await()
|
||||||
|
database.videoDao.insertAll(playlist.asDatabaseModel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.example.android.devbyteviewer.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a single activity application that uses the Navigation library. Content is displayed
|
||||||
|
* by Fragments.
|
||||||
|
*/
|
||||||
|
class DevByteActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity is starting. This is where most initialization
|
||||||
|
* should go
|
||||||
|
*/
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_dev_byte_viewer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.example.android.devbyteviewer.R
|
||||||
|
import com.example.android.devbyteviewer.databinding.DevbyteItemBinding
|
||||||
|
import com.example.android.devbyteviewer.databinding.FragmentDevByteBinding
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.viewmodels.DevByteViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a list of DevBytes on screen.
|
||||||
|
*/
|
||||||
|
class DevByteFragment : Fragment() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One way to delay creation of the viewModel until an appropriate lifecycle method is to use
|
||||||
|
* lazy. This requires that viewModel not be referenced before onActivityCreated, which we
|
||||||
|
* do in this Fragment.
|
||||||
|
*/
|
||||||
|
private val viewModel: DevByteViewModel by lazy {
|
||||||
|
val activity = requireNotNull(this.activity) {
|
||||||
|
"You can only access the viewModel after onActivityCreated()"
|
||||||
|
}
|
||||||
|
ViewModelProviders.of(this, DevByteViewModel.Factory(activity.application))
|
||||||
|
.get(DevByteViewModel::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView Adapter for converting a list of Video to cards.
|
||||||
|
*/
|
||||||
|
private var viewModelAdapter: DevByteAdapter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the fragment's activity has been created and this
|
||||||
|
* fragment's view hierarchy instantiated. It can be used to do final
|
||||||
|
* initialization once these pieces are in place, such as retrieving
|
||||||
|
* views or restoring state.
|
||||||
|
*/
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
viewModel.playlist.observe(viewLifecycleOwner, Observer<List<DevByteVideo>> { videos ->
|
||||||
|
videos?.apply {
|
||||||
|
viewModelAdapter?.videos = videos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to have the fragment instantiate its user interface view.
|
||||||
|
*
|
||||||
|
* <p>If you return a View from here, you will later be called in
|
||||||
|
* {@link #onDestroyView} when the view is being released.
|
||||||
|
*
|
||||||
|
* @param inflater The LayoutInflater object that can be used to inflate
|
||||||
|
* any views in the fragment,
|
||||||
|
* @param container If non-null, this is the parent view that the fragment's
|
||||||
|
* UI should be attached to. The fragment should not add the view itself,
|
||||||
|
* but this can be used to generate the LayoutParams of the view.
|
||||||
|
* @param savedInstanceState If non-null, this fragment is being re-constructed
|
||||||
|
* from a previous saved state as given here.
|
||||||
|
*
|
||||||
|
* @return Return the View for the fragment's UI.
|
||||||
|
*/
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
val binding: FragmentDevByteBinding = DataBindingUtil.inflate(
|
||||||
|
inflater,
|
||||||
|
R.layout.fragment_dev_byte,
|
||||||
|
container,
|
||||||
|
false)
|
||||||
|
// Set the lifecycleOwner so DataBinding can observe LiveData
|
||||||
|
binding.setLifecycleOwner(viewLifecycleOwner)
|
||||||
|
|
||||||
|
binding.viewModel = viewModel
|
||||||
|
|
||||||
|
viewModelAdapter = DevByteAdapter(VideoClick {
|
||||||
|
// When a video is clicked this block or lambda will be called by DevByteAdapter
|
||||||
|
|
||||||
|
// context is not around, we can safely discard this click since the Fragment is no
|
||||||
|
// longer on the screen
|
||||||
|
val packageManager = context?.packageManager ?: return@VideoClick
|
||||||
|
|
||||||
|
// Try to generate a direct intent to the YouTube app
|
||||||
|
var intent = Intent(Intent.ACTION_VIEW, it.launchUri)
|
||||||
|
if(intent.resolveActivity(packageManager) == null) {
|
||||||
|
// YouTube app isn't found, use the web url
|
||||||
|
intent = Intent(Intent.ACTION_VIEW, Uri.parse(it.url))
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.root.findViewById<RecyclerView>(R.id.recycler_view).apply {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = viewModelAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Observer for the network error.
|
||||||
|
viewModel.eventNetworkError.observe(this, Observer<Boolean> { isNetworkError ->
|
||||||
|
if (isNetworkError) onNetworkError()
|
||||||
|
})
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for displaying a Toast error message for network errors.
|
||||||
|
*/
|
||||||
|
private fun onNetworkError() {
|
||||||
|
if(!viewModel.isNetworkErrorShown.value!!) {
|
||||||
|
Toast.makeText(activity, "Network Error", Toast.LENGTH_LONG).show()
|
||||||
|
viewModel.onNetworkErrorShown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to generate YouTube app links
|
||||||
|
*/
|
||||||
|
private val DevByteVideo.launchUri: Uri
|
||||||
|
get() {
|
||||||
|
val httpUri = Uri.parse(url)
|
||||||
|
return Uri.parse("vnd.youtube:" + httpUri.getQueryParameter("v"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Click listener for Videos. By giving the block a name it helps a reader understand what it does.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class VideoClick(val block: (DevByteVideo) -> Unit) {
|
||||||
|
/**
|
||||||
|
* Called when a video is clicked
|
||||||
|
*
|
||||||
|
* @param video the video that was clicked
|
||||||
|
*/
|
||||||
|
fun onClick(video: DevByteVideo) = block(video)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView Adapter for setting up data binding on the items in the list.
|
||||||
|
*/
|
||||||
|
class DevByteAdapter(val callback: VideoClick) : RecyclerView.Adapter<DevByteViewHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The videos that our Adapter will show
|
||||||
|
*/
|
||||||
|
var videos: List<DevByteVideo> = emptyList()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
// For an extra challenge, update this to use the paging library.
|
||||||
|
|
||||||
|
// Notify any registered observers that the data set has changed. This will cause every
|
||||||
|
// element in our RecyclerView to be invalidated.
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
|
||||||
|
* an item.
|
||||||
|
*/
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DevByteViewHolder {
|
||||||
|
val withDataBinding: DevbyteItemBinding = DataBindingUtil.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
DevByteViewHolder.LAYOUT,
|
||||||
|
parent,
|
||||||
|
false)
|
||||||
|
return DevByteViewHolder(withDataBinding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = videos.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by RecyclerView to display the data at the specified position. This method should
|
||||||
|
* update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
|
||||||
|
* position.
|
||||||
|
*/
|
||||||
|
override fun onBindViewHolder(holder: DevByteViewHolder, position: Int) {
|
||||||
|
holder.viewDataBinding.also {
|
||||||
|
it.video = videos[position]
|
||||||
|
it.videoCallback = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for DevByte items. All work is done by data binding.
|
||||||
|
*/
|
||||||
|
class DevByteViewHolder(val viewDataBinding: DevbyteItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(viewDataBinding.root) {
|
||||||
|
companion object {
|
||||||
|
@LayoutRes
|
||||||
|
val LAYOUT = R.layout.devbyte_item
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.util
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binding adapter used to hide the spinner once data is available.
|
||||||
|
*/
|
||||||
|
@BindingAdapter("isNetworkError", "playlist")
|
||||||
|
fun hideIfNetworkError(view: View, isNetWorkError: Boolean, playlist: Any?) {
|
||||||
|
view.visibility = if (playlist != null) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
if(isNetWorkError) {
|
||||||
|
view.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binding adapter used to display images from URL using Glide
|
||||||
|
*/
|
||||||
|
@BindingAdapter("imageUrl")
|
||||||
|
fun setImageUrl(imageView: ImageView, url: String) {
|
||||||
|
Glide.with(imageView.context).load(url).into(imageView)
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.util
|
||||||
|
|
||||||
|
private val PUNCTUATION = listOf(", ", "; ", ": ", " ")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate long text with a preference for word boundaries and without trailing punctuation.
|
||||||
|
*/
|
||||||
|
fun String.smartTruncate(length: Int): String {
|
||||||
|
val words = split(" ")
|
||||||
|
var added = 0
|
||||||
|
var hasMore = false
|
||||||
|
val builder = StringBuilder()
|
||||||
|
for (word in words) {
|
||||||
|
if (builder.length > length) {
|
||||||
|
hasMore = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
builder.append(word)
|
||||||
|
builder.append(" ")
|
||||||
|
added += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
PUNCTUATION.map {
|
||||||
|
if (builder.endsWith(it)) {
|
||||||
|
builder.replace(builder.length - it.length, builder.length, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMore) {
|
||||||
|
builder.append("...")
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.example.android.devbyteviewer.database.getDatabase
|
||||||
|
import com.example.android.devbyteviewer.domain.DevByteVideo
|
||||||
|
import com.example.android.devbyteviewer.network.DevByteNetwork
|
||||||
|
import com.example.android.devbyteviewer.network.asDomainModel
|
||||||
|
import com.example.android.devbyteviewer.repository.VideosRepository
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DevByteViewModel designed to store and manage UI-related data in a lifecycle conscious way. This
|
||||||
|
* allows data to survive configuration changes such as screen rotations. In addition, background
|
||||||
|
* work such as fetching network results can continue through configuration changes and deliver
|
||||||
|
* results after the new Fragment or Activity is available.
|
||||||
|
*
|
||||||
|
* @param application The application that this viewmodel is attached to, it's safe to hold a
|
||||||
|
* reference to applications across rotation since Application is never recreated during actiivty
|
||||||
|
* or fragment lifecycle events.
|
||||||
|
*/
|
||||||
|
class DevByteViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data source this ViewModel will fetch results from.
|
||||||
|
*/
|
||||||
|
private val videosRepository = VideosRepository(getDatabase(application))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A playlist of videos displayed on the screen.
|
||||||
|
*/
|
||||||
|
val playlist = videosRepository.videos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the job for all coroutines started by this ViewModel.
|
||||||
|
*
|
||||||
|
* Cancelling this job will cancel all coroutines started by this ViewModel.
|
||||||
|
*/
|
||||||
|
private val viewModelJob = SupervisorJob()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the main scope for all coroutines launched by MainViewModel.
|
||||||
|
*
|
||||||
|
* Since we pass viewModelJob, you can cancel all coroutines launched by uiScope by calling
|
||||||
|
* viewModelJob.cancel()
|
||||||
|
*/
|
||||||
|
private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered for network error. This is private to avoid exposing a
|
||||||
|
* way to set this value to observers.
|
||||||
|
*/
|
||||||
|
private var _eventNetworkError = MutableLiveData<Boolean>(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered for network error. Views should use this to get access
|
||||||
|
* to the data.
|
||||||
|
*/
|
||||||
|
val eventNetworkError: LiveData<Boolean>
|
||||||
|
get() = _eventNetworkError
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to display the error message. This is private to avoid exposing a
|
||||||
|
* way to set this value to observers.
|
||||||
|
*/
|
||||||
|
private var _isNetworkErrorShown = MutableLiveData<Boolean>(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to display the error message. Views should use this to get access
|
||||||
|
* to the data.
|
||||||
|
*/
|
||||||
|
val isNetworkErrorShown: LiveData<Boolean>
|
||||||
|
get() = _isNetworkErrorShown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init{} is called immediately when this ViewModel is created.
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
refreshDataFromRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh data from the repository. Use a coroutine launch to run in a
|
||||||
|
* background thread.
|
||||||
|
*/
|
||||||
|
private fun refreshDataFromRepository() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
videosRepository.refreshVideos()
|
||||||
|
_eventNetworkError.value = false
|
||||||
|
_isNetworkErrorShown.value = false
|
||||||
|
|
||||||
|
} catch (networkError: IOException) {
|
||||||
|
// Show a Toast error message and hide the progress bar.
|
||||||
|
if(playlist.value!!.isEmpty())
|
||||||
|
_eventNetworkError.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the network error flag.
|
||||||
|
*/
|
||||||
|
fun onNetworkErrorShown() {
|
||||||
|
_isNetworkErrorShown.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel all coroutines when the ViewModel is cleared
|
||||||
|
*/
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
viewModelJob.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for constructing DevByteViewModel with parameter
|
||||||
|
*/
|
||||||
|
class Factory(val app: Application) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(DevByteViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return DevByteViewModel(app) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unable to construct viewmodel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.devbyteviewer.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.example.android.devbyteviewer.database.getDatabase
|
||||||
|
import com.example.android.devbyteviewer.repository.VideosRepository
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
|
||||||
|
CoroutineWorker(appContext, params) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
|
||||||
|
}
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val database = getDatabase(applicationContext)
|
||||||
|
val repository = VideosRepository(database)
|
||||||
|
|
||||||
|
try {
|
||||||
|
repository.refreshVideos( )
|
||||||
|
Timber.d("WorkManager: Work request for sync is run")
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
return Result.retry()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
}
|
||||||
50
DevBytesWorkManager/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Executable file
@ -0,0 +1,50 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
186
DevBytesWorkManager/app/src/main/res/drawable/ic_launcher_background.xml
Executable file
@ -0,0 +1,186 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#008577"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="48"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:width="24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M20,33l12,-9 -12,-9v18zM24,4C12.95,4 4,12.95 4,24s8.95,20 20,20 20,-8.95 20,-20S35.05,4 24,4zM24,40c-8.82,0 -16,-7.18 -16,-16S15.18,8 24,8s16,7.18 16,16 -7.18,16 -16,16z" />
|
||||||
|
</vector>
|
||||||
33
DevBytesWorkManager/app/src/main/res/layout/activity_dev_byte_viewer.xml
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.DevByteActivity">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/my_nav_host_fragment"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
app:navGraph="@navigation/nav_graph"
|
||||||
|
app:defaultNavHost="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
130
DevBytesWorkManager/app/src/main/res/layout/devbyte_item.xml
Executable file
@ -0,0 +1,130 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="video"
|
||||||
|
type="com.example.android.devbyteviewer.domain.DevByteVideo" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="videoCallback"
|
||||||
|
type="com.example.android.devbyteviewer.ui.VideoClick" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="0dp"
|
||||||
|
app:cardElevation="5dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/left_well"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_begin="8dp" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Guideline
|
||||||
|
android:id="@+id/right_well"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintGuide_end="8dp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/play_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/left_well"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_thumbnail"
|
||||||
|
app:srcCompat="@drawable/ic_play_circle_outline_black_48dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@{video.title}"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/text_black"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/right_well"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/play_icon"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/video_thumbnail"
|
||||||
|
tools:text="Video title" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@{video.shortDescription}"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/right_well"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/left_well"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:text="this is a video @android:string/fingerprint_icon_content_description" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/video_thumbnail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:adjustViewBounds="false"
|
||||||
|
android:cropToPadding="false"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:imageUrl="@{video.thumbnail}"
|
||||||
|
app:layout_constraintDimensionRatio="h,4:3"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:srcCompat="@tools:sample/backgrounds/scenic" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/clickableOverlay"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:onClick="@{() -> videoCallback.onClick(video)}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</layout>
|
||||||
53
DevBytesWorkManager/app/src/main/res/layout/fragment_dev_byte.xml
Executable file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".ui.DevByteFragment">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.example.android.devbyteviewer.viewmodels.DevByteViewModel" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading_spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:isNetworkError = "@{safeUnbox(viewModel.eventNetworkError)}"
|
||||||
|
app:playlist = "@{viewModel.playlist}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:listitem="@layout/devbyte_item" />
|
||||||
|
</FrameLayout>
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
|
||||||
21
DevBytesWorkManager/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
21
DevBytesWorkManager/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
BIN
DevBytesWorkManager/app/src/main/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-mdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
DevBytesWorkManager/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
29
DevBytesWorkManager/app/src/main/res/navigation/nav_graph.xml
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/nav_graph"
|
||||||
|
app:startDestination="@id/devByte">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/devByte"
|
||||||
|
android:name="com.example.android.devbyteviewer.ui.DevByteFragment"
|
||||||
|
android:label="fragment_dev_byte"
|
||||||
|
tools:layout="@layout/fragment_dev_byte" />
|
||||||
|
</navigation>
|
||||||
26
DevBytesWorkManager/app/src/main/res/values/colors.xml
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#1E8E3E</color>
|
||||||
|
<color name="colorPrimaryDark">#0D652D</color>
|
||||||
|
<color name="colorAccent">#E37400</color>
|
||||||
|
|
||||||
|
<color name="background_grey">#ffDDDDDD</color>
|
||||||
|
<color name="text_light">#ff666666</color>
|
||||||
|
<color name="text_black">#ff222222</color>
|
||||||
|
</resources>
|
||||||
19
DevBytesWorkManager/app/src/main/res/values/strings.xml
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">DevByte Viewer</string>
|
||||||
|
</resources>
|
||||||
27
DevBytesWorkManager/app/src/main/res/values/styles.xml
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2019 Google Inc.
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
||||||
45
DevBytesWorkManager/build.gradle
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.31'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
maven { url "https://kotlin.bintray.com/kotlinx/" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
31
DevBytesWorkManager/gradle.properties
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2019 Google Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
BIN
DevBytesWorkManager/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
6
DevBytesWorkManager/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#Thu Apr 25 13:35:31 PDT 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||||
172
DevBytesWorkManager/gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
DevBytesWorkManager/gradlew.bat
vendored
Executable file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
17
DevBytesWorkManager/settings.gradle
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
include ':app'
|
||||||
56
MarsRealEstateFinal/README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
MarsRealEstateFinal - Solution Code
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Solution code for Android Kotlin Fundamentals Codelab 8.3 Filtering and detail views with
|
||||||
|
internet data.
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
|
MarsRealEstate is a demo app that shows available properties for sale and for rent on Mars.
|
||||||
|
The property data is stored on a Web server as a REST web service. This app demonstrated
|
||||||
|
the use of [Retrofit](https://square.github.io/retrofit/) to make REST requests to the
|
||||||
|
web service, [Moshi](https://github.com/square/moshi) to handle the deserialization of the
|
||||||
|
returned JSON to Kotlin data objects, and [Glide](https://bumptech.github.io/glide/) to load and
|
||||||
|
cache images by URL.
|
||||||
|
|
||||||
|
The app also leverages [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel),
|
||||||
|
[LiveData](https://developer.android.com/topic/libraries/architecture/livedata),
|
||||||
|
[Data Binding](https://developer.android.com/topic/libraries/data-binding/) with binding
|
||||||
|
adapters, and [Navigation](https://developer.android.com/topic/libraries/architecture/navigation/)
|
||||||
|
with the SafeArgs plugin for parameter passing between fragments.
|
||||||
|
|
||||||
|
Pre-requisites
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You need to know:
|
||||||
|
- How to create and use fragments.
|
||||||
|
- How to navigate between fragments, and use safeArgs to pass data between fragments.
|
||||||
|
- How to use architecture components including ViewModel, ViewModelProvider.Factory, LiveData, and LiveData transformations.
|
||||||
|
- How to use coroutines for long-running tasks.
|
||||||
|
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
1. Download and run the app.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2019 Google, Inc.
|
||||||
|
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more contributor
|
||||||
|
license agreements. See the NOTICE file distributed with this work for
|
||||||
|
additional information regarding copyright ownership. The ASF licenses this
|
||||||
|
file to you under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License.
|
||||||
1
MarsRealEstateFinal/app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
87
MarsRealEstateFinal/app/build.gradle
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: "androidx.navigation.safeargs"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.example.android.marsrealestate"
|
||||||
|
minSdkVersion 19
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
// Kotlin
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$version_kotlin"
|
||||||
|
|
||||||
|
// Constraint Layout
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:$version_constraint_layout"
|
||||||
|
|
||||||
|
// ViewModel and LiveData
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:$version_lifecycle_extensions"
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
implementation "android.arch.navigation:navigation-fragment-ktx:$version_navigation"
|
||||||
|
implementation "android.arch.navigation:navigation-ui-ktx:$version_navigation"
|
||||||
|
|
||||||
|
// Core with Ktx
|
||||||
|
implementation "androidx.core:core-ktx:$version_core"
|
||||||
|
|
||||||
|
// Moshi
|
||||||
|
implementation "com.squareup.moshi:moshi:$version_moshi"
|
||||||
|
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
|
||||||
|
|
||||||
|
// Retrofit with Moshi Converter
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
|
||||||
|
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
|
||||||
|
|
||||||
|
// Coroutines
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines"
|
||||||
|
|
||||||
|
// Retrofit Coroutines Support
|
||||||
|
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
|
||||||
|
|
||||||
|
// Glide
|
||||||
|
implementation "com.github.bumptech.glide:glide:$version_glide"
|
||||||
|
|
||||||
|
// RecyclerView
|
||||||
|
implementation "androidx.recyclerview:recyclerview:$version_recyclerview"
|
||||||
|
}
|
||||||
21
MarsRealEstateFinal/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
40
MarsRealEstateFinal/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
~ Copyright 2019, The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
~
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.marsrealestate">
|
||||||
|
|
||||||
|
<!-- In order for our app to access the Internet, we need to define this permission. -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
<activity android:name="com.example.android.marsrealestate.MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
BIN
MarsRealEstateFinal/app/src/main/ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.marsrealestate
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.example.android.marsrealestate.network.MarsProperty
|
||||||
|
import com.example.android.marsrealestate.overview.MarsApiStatus
|
||||||
|
import com.example.android.marsrealestate.overview.PhotoGridAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When there is no Mars property data (data is null), hide the [RecyclerView], otherwise show it.
|
||||||
|
*/
|
||||||
|
@BindingAdapter("listData")
|
||||||
|
fun bindRecyclerView(recyclerView: RecyclerView, data: List<MarsProperty>?) {
|
||||||
|
val adapter = recyclerView.adapter as PhotoGridAdapter
|
||||||
|
adapter.submitList(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the Glide library to load an image by URL into an [ImageView]
|
||||||
|
*/
|
||||||
|
@BindingAdapter("imageUrl")
|
||||||
|
fun bindImage(imgView: ImageView, imgUrl: String?) {
|
||||||
|
imgUrl?.let {
|
||||||
|
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
|
||||||
|
Glide.with(imgView.context)
|
||||||
|
.load(imgUri)
|
||||||
|
.apply(RequestOptions()
|
||||||
|
.placeholder(R.drawable.loading_animation)
|
||||||
|
.error(R.drawable.ic_broken_image))
|
||||||
|
.into(imgView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This binding adapter displays the [MarsApiStatus] of the network request in an image view. When
|
||||||
|
* the request is loading, it displays a loading_animation. If the request has an error, it
|
||||||
|
* displays a broken image to reflect the connection error. When the request is finished, it
|
||||||
|
* hides the image view.
|
||||||
|
*/
|
||||||
|
@BindingAdapter("marsApiStatus")
|
||||||
|
fun bindStatus(statusImageView: ImageView, status: MarsApiStatus?) {
|
||||||
|
when (status) {
|
||||||
|
MarsApiStatus.LOADING -> {
|
||||||
|
statusImageView.visibility = View.VISIBLE
|
||||||
|
statusImageView.setImageResource(R.drawable.loading_animation)
|
||||||
|
}
|
||||||
|
MarsApiStatus.ERROR -> {
|
||||||
|
statusImageView.visibility = View.VISIBLE
|
||||||
|
statusImageView.setImageResource(R.drawable.ic_connection_error)
|
||||||
|
}
|
||||||
|
MarsApiStatus.DONE -> {
|
||||||
|
statusImageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.marsrealestate
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our MainActivity is only responsible for setting the content view that contains the
|
||||||
|
* Navigation Host.
|
||||||
|
*/
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019, The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.marsrealestate.detail
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProviders
|
||||||
|
import com.example.android.marsrealestate.databinding.FragmentDetailBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This [Fragment] shows the detailed information about a selected piece of Mars real estate.
|
||||||
|
* It sets this information in the [DetailViewModel], which it gets as a Parcelable property
|
||||||
|
* through Jetpack Navigation's SafeArgs.
|
||||||
|
*/
|
||||||
|
class DetailFragment : Fragment() {
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
|
||||||
|
val application = requireNotNull(activity).application
|
||||||
|
val binding = FragmentDetailBinding.inflate(inflater)
|
||||||
|
binding.setLifecycleOwner(this)
|
||||||
|
|
||||||
|
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
|
||||||
|
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
|
||||||
|
binding.viewModel = ViewModelProviders.of(
|
||||||
|
this, viewModelFactory).get(DetailViewModel::class.java)
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
||||||