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
|
||||
}
|
||||
}
|
||||