GDGFinder
Change-Id: Ia17c4383f879a5f2a7ee839920516cd8feeea373
201
GDGFinderFinal/LICENSE
Executable file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
1
GDGFinderFinal/README.md
Executable file
@ -0,0 +1 @@
|
||||
# andfun-kotlin-gdg-finder
|
||||
1
GDGFinderFinal/app/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/build
|
||||
96
GDGFinderFinal/app/build.gradle
Executable file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2018, 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.gdgfinder"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
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"
|
||||
// use viewModelScope from lifecycle-viewmodel-ktx
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-alpha03"
|
||||
|
||||
// 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
|
||||
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"
|
||||
|
||||
// material design components
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
|
||||
// Client for retrieving location
|
||||
implementation "com.google.android.gms:play-services-location:16.0.0"
|
||||
}
|
||||
21
GDGFinderFinal/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
|
||||
32
GDGFinderFinal/app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,32 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.example.android.gdgfinder">
|
||||
|
||||
<!-- In order for our app to access the Internet, we need to define this permission. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- To access the location we need to ask for permission -->
|
||||
<!-- see https://developer.android.com/training/location/retrieve-current.html -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<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"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
android:resource="@array/preloaded_fonts" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.gdgfinder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.search.GdgListAdapter
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
||||
/**
|
||||
* When there is no Mars property data (data is null), hide the [RecyclerView], otherwise show it.
|
||||
*/
|
||||
@BindingAdapter("listData")
|
||||
fun bindRecyclerView(recyclerView: RecyclerView, data: List<GdgChapter>?) {
|
||||
val adapter = recyclerView.adapter as GdgListAdapter
|
||||
adapter.submitList(data) {
|
||||
// scroll the list to the top after the diffs are calculated and posted
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("showOnlyWhenEmpty")
|
||||
fun View.showOnlyWhenEmpty(data: List<GdgChapter>?) {
|
||||
visibility = when {
|
||||
data == null || data.isEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.gdgfinder
|
||||
|
||||
import android.os.Bundle
|
||||
import android.transition.TransitionManager
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.NavigationUI.navigateUp
|
||||
import androidx.navigation.ui.NavigationUI.setupWithNavController
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.example.android.gdgfinder.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
||||
|
||||
setupNavigation()
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the hamburger menu or back button are pressed on the Toolbar
|
||||
*
|
||||
* Delegate this to Navigation.
|
||||
*/
|
||||
override fun onSupportNavigateUp() = navigateUp(findNavController(R.id.nav_host_fragment), binding.drawerLayout)
|
||||
|
||||
/**
|
||||
* Setup Navigation for this Activity
|
||||
*/
|
||||
private fun setupNavigation() {
|
||||
// first find the nav controller
|
||||
val navController = findNavController(R.id.nav_host_fragment)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
// then setup the action bar, tell it about the DrawerLayout
|
||||
setupActionBarWithNavController(navController, binding.drawerLayout)
|
||||
|
||||
|
||||
// finally setup the left drawer (called a NavigationView)
|
||||
binding.navigationView.setupWithNavController(navController)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination: NavDestination, _ ->
|
||||
val toolBar = supportActionBar ?: return@addOnDestinationChangedListener
|
||||
when(destination.id) {
|
||||
R.id.home -> {
|
||||
toolBar.setDisplayShowTitleEnabled(false)
|
||||
binding.heroImage.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
toolBar.setDisplayShowTitleEnabled(true)
|
||||
binding.heroImage.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.gdgfinder.add
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.example.android.gdgfinder.R
|
||||
import com.example.android.gdgfinder.databinding.AddGdgFragmentBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
class AddGdgFragment : Fragment() {
|
||||
|
||||
private val viewModel: AddGdgViewModel by lazy {
|
||||
ViewModelProviders.of(this).get(AddGdgViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
val binding = AddGdgFragmentBinding.inflate(inflater)
|
||||
|
||||
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
|
||||
binding.setLifecycleOwner(this)
|
||||
|
||||
binding.viewModel = viewModel
|
||||
|
||||
viewModel.showSnackBarEvent.observe(this, Observer {
|
||||
if (it == true) { // Observed state is true.
|
||||
Snackbar.make(
|
||||
activity!!.findViewById(android.R.id.content),
|
||||
getString(R.string.application_submitted),
|
||||
Snackbar.LENGTH_SHORT // How long to display the message.
|
||||
).show()
|
||||
viewModel.doneShowingSnackbar()
|
||||
binding.button.contentDescription=getString(R.string.submitted)
|
||||
binding.button.text=getString(R.string.done)
|
||||
}
|
||||
})
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.gdgfinder.add
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
class AddGdgViewModel : ViewModel() {
|
||||
|
||||
/**
|
||||
* Request a toast by setting this value to true.
|
||||
*
|
||||
* This is private because we don't want to expose setting this value to the Fragment.
|
||||
*/
|
||||
private var _showSnackbarEvent = MutableLiveData<Boolean?>()
|
||||
|
||||
/**
|
||||
* If this is true, immediately `show()` a toast and call `doneShowingSnackbar()`.
|
||||
*/
|
||||
val showSnackBarEvent: LiveData<Boolean?>
|
||||
get() = _showSnackbarEvent
|
||||
|
||||
/**
|
||||
* Call this immediately after calling `show()` on a toast.
|
||||
*
|
||||
* It will clear the toast request, so if the user rotates their phone it won't show a duplicate
|
||||
* toast.
|
||||
*/
|
||||
fun doneShowingSnackbar() {
|
||||
_showSnackbarEvent.value = null
|
||||
}
|
||||
|
||||
fun onSubmitApplication() {
|
||||
_showSnackbarEvent.value = true
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.gdgfinder.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.example.android.gdgfinder.R
|
||||
import com.example.android.gdgfinder.databinding.HomeFragmentBinding
|
||||
|
||||
class HomeFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = HomeFragment()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: HomeViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val binding = HomeFragmentBinding.inflate(inflater)
|
||||
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
|
||||
|
||||
binding.viewModel = viewModel
|
||||
|
||||
viewModel.navigateToSearch.observe(viewLifecycleOwner,
|
||||
Observer<Boolean> { navigate ->
|
||||
if(navigate) {
|
||||
val navController = findNavController()
|
||||
navController.navigate(R.id.action_homeFragment_to_gdgListFragment)
|
||||
viewModel.onNavigatedToSearch()
|
||||
}
|
||||
})
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.gdgfinder.home
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
|
||||
private val _navigateToSearch = MutableLiveData<Boolean>()
|
||||
val navigateToSearch: LiveData<Boolean>
|
||||
get() = _navigateToSearch
|
||||
|
||||
fun onFabClicked() {
|
||||
_navigateToSearch.value = true
|
||||
}
|
||||
|
||||
fun onNavigatedToSearch() {
|
||||
_navigateToSearch.value = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.gdgfinder.network
|
||||
|
||||
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.GET
|
||||
|
||||
// The alternative URL is for a server with a recent snapshot. If you are having problems
|
||||
// with the given URL (app crashing), use the alternative.
|
||||
//private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com/"
|
||||
private const val BASE_URL = "https://developers.google.com/community/gdg/directory/"
|
||||
|
||||
interface GdgApiService {
|
||||
//@GET("gdg-directory.json")
|
||||
@GET("directory.json")
|
||||
|
||||
fun getChapters():
|
||||
// The Coroutine Call Adapter allows us to return a Deferred, a Job with a result
|
||||
Deferred<GdgResponse>
|
||||
}
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
private val retrofit = Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||
.baseUrl(BASE_URL)
|
||||
.build()
|
||||
|
||||
object GdgApi {
|
||||
val retrofitService: GdgApiService by lazy { retrofit.create(GdgApiService::class.java) }
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.gdgfinder.network
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class GdgChapter(
|
||||
@Json(name = "chapter_name") val name: String,
|
||||
@Json(name = "cityarea") val city: String,
|
||||
val country: String,
|
||||
val region: String,
|
||||
val website: String,
|
||||
val geo: LatLong
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class LatLong(
|
||||
val lat: Double,
|
||||
@Json(name = "lng")
|
||||
val long: Double
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class GdgResponse(
|
||||
@Json(name = "filters_") val filters: Filter,
|
||||
@Json(name = "data") val chapters: List<GdgChapter>
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Filter(
|
||||
@Json(name = "region") val regions: List<String>
|
||||
): Parcelable
|
||||
|
||||
//"chapter_name": "GDG Bordj Bou-Arréridj",
|
||||
//"cityarea": "Burj Bu Arririj",
|
||||
//"country": "Algeria",
|
||||
//"region": "Africa",
|
||||
//"website": "https://www.meetup.com/GDG-BBA",
|
||||
//"geo": {
|
||||
// "lat": 36.06000137,
|
||||
// "lng": 4.630000114
|
||||
//}
|
||||
@ -0,0 +1,200 @@
|
||||
package com.example.android.gdgfinder.search
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
import android.location.Location
|
||||
import com.example.android.gdgfinder.network.GdgApiService
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.network.GdgResponse
|
||||
import com.example.android.gdgfinder.network.LatLong
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GdgChapterRepository(gdgApiService: GdgApiService) {
|
||||
|
||||
/**
|
||||
* A single network request, the results won't change. For this lesson we did not add an offline cache for simplicity
|
||||
* and the result will be cached in memory.
|
||||
*/
|
||||
private val request = gdgApiService.getChapters()
|
||||
|
||||
/**
|
||||
* An in-progress (or potentially completed) sort, this may be null or cancelled at any time.
|
||||
*
|
||||
* If this is non-null, calling await will get the result of the last sorting request.
|
||||
*
|
||||
* This will be cancelled whenever location changes, as the old results are no longer valid.
|
||||
*/
|
||||
private var inProgressSort: Deferred<SortedData>? = null
|
||||
|
||||
var isFullyInitialized = false
|
||||
private set
|
||||
|
||||
|
||||
/**
|
||||
* Get the chapters list for a specified filter.
|
||||
*
|
||||
* This will be cancel if a new location is sent before the result is available.
|
||||
*
|
||||
* This works by first waiting for any previously in-progress sorts, and if a sort has not yet started
|
||||
* it will start a new sort (which may happen if location is disabled on the device)
|
||||
*/
|
||||
suspend fun getChaptersForFilter(filter: String?): List<GdgChapter> {
|
||||
val data = sortedData()
|
||||
return when(filter) {
|
||||
null -> data.chapters
|
||||
else -> data.chaptersByRegion.getOrElse(filter) { emptyList() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filters sorted by distance from the last location.
|
||||
*
|
||||
* This will cancel if a new location is sent before the result is available.
|
||||
*
|
||||
* This works by first waiting for any previously in-progress sorts, and if a sort has not yet started
|
||||
* it will start a new sort (which may happen if location is disabled on the device)
|
||||
*/
|
||||
suspend fun getFilters(): List<String> = sortedData().filters
|
||||
|
||||
/**
|
||||
* Get the computed sorted data after it completes, or start a new sort if none are running.
|
||||
*
|
||||
* This will always cancel if the location changes while the sort is in progress.
|
||||
*/
|
||||
private suspend fun sortedData(): SortedData = withContext(Dispatchers.Main) {
|
||||
// We need to ensure we're on Dispatchers.Main so that this is not running on multiple Dispatchers and we
|
||||
// modify the member inProgressSort.
|
||||
|
||||
// Since this was called from viewModelScope, that will always be a simple if check (not expensive), but
|
||||
// by specifying the dispatcher we can protect against incorrect usage.
|
||||
|
||||
// if there's currently a sort running (or completed) wait for it to complete and return that value
|
||||
// otherwise, start a new sort with no location (the user has likely not given us permission to use location
|
||||
// yet)
|
||||
inProgressSort?.await() ?: doSortData()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to force a new sort to start.
|
||||
*
|
||||
* This will start a new coroutine to perform the sort. Future requests to sorted data can use the deferred in
|
||||
* [inProgressSort] to get the result of the last sort without sorting the data again. This guards against multiple
|
||||
* sorts being performed on the same data, which is inefficient.
|
||||
*
|
||||
* This will always cancel if the location changes while the sort is in progress.
|
||||
*
|
||||
* @return the result of the started sort
|
||||
*/
|
||||
private suspend fun doSortData(location: Location? = null): SortedData {
|
||||
// since we'll need to launch a new coroutine for the sorting use coroutineScope.
|
||||
// coroutineScope will automatically wait for anything started via async {} or await{} in it's block to
|
||||
// complete.
|
||||
val result = coroutineScope {
|
||||
// launch a new coroutine to do the sort (so other requests can wait for this sort to complete)
|
||||
val deferred = async { SortedData.from(request.await(), location) }
|
||||
// cache the Deferred so any future requests can wait for this sort
|
||||
inProgressSort = deferred
|
||||
// and return the result of this sort
|
||||
deferred.await()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when location changes.
|
||||
*
|
||||
* This will cancel any previous queries, so it's important to re-request the data after calling this function.
|
||||
*
|
||||
* @param location the location to sort by
|
||||
*/
|
||||
suspend fun onLocationChanged(location: Location) {
|
||||
// We need to ensure we're on Dispatchers.Main so that this is not running on multiple Dispatchers and we
|
||||
// modify the member inProgressSort.
|
||||
|
||||
// Since this was called from viewModelScope, that will always be a simple if check (not expensive), but
|
||||
// by specifying the dispatcher we can protect against incorrect usage.
|
||||
withContext(Dispatchers.Main) {
|
||||
isFullyInitialized = true
|
||||
|
||||
// cancel any in progress sorts, their result is not valid anymore.
|
||||
inProgressSort?.cancel()
|
||||
|
||||
doSortData(location)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds data sorted by the distance from the last location.
|
||||
*
|
||||
* Note, by convention this class won't sort on the Main thread. This is not a public API and should
|
||||
* only be called by [doSortData].
|
||||
*/
|
||||
private class SortedData private constructor(
|
||||
val chapters: List<GdgChapter>,
|
||||
val filters: List<String>,
|
||||
val chaptersByRegion: Map<String, List<GdgChapter>>
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Sort the data from a [GdgResponse] by the specified location.
|
||||
*
|
||||
* @param response the response to sort
|
||||
* @param location the location to sort by, if null the data will not be sorted.
|
||||
*/
|
||||
suspend fun from(response: GdgResponse, location: Location?): SortedData {
|
||||
return withContext(Dispatchers.Default) {
|
||||
// this sorting is too expensive to do on the main thread, so do thread confinement here.
|
||||
val chapters: List<GdgChapter> = response.chapters.sortByDistanceFrom(location)
|
||||
// use distinctBy which will maintain the input order - this will have the effect of making
|
||||
// a filter list sorted by the distance from the current location
|
||||
val filters: List<String> = chapters.map { it.region } .distinctBy { it }
|
||||
// group the chapters by region so that filter queries don't require any work
|
||||
val chaptersByRegion: Map<String, List<GdgChapter>> = chapters.groupBy { it.region }
|
||||
// return the sorted result
|
||||
SortedData(chapters, filters, chaptersByRegion)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort a list of GdgChapter by their distance from the specified location.
|
||||
*
|
||||
* @param currentLocation returned list will be sorted by the distance, or unsorted if null
|
||||
*/
|
||||
private fun List<GdgChapter>.sortByDistanceFrom(currentLocation: Location?): List<GdgChapter> {
|
||||
currentLocation ?: return this
|
||||
|
||||
return sortedBy { distanceBetween(it.geo, currentLocation)}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance (in meters) between a LatLong and a Location.
|
||||
*/
|
||||
private fun distanceBetween(start: LatLong, currentLocation: Location): Float {
|
||||
val results = FloatArray(3)
|
||||
Location.distanceBetween(start.lat, start.long, currentLocation.latitude, currentLocation.longitude, results)
|
||||
return results[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.search.GdgListAdapter.GdgListViewHolder
|
||||
import com.example.android.gdgfinder.databinding.ListItemBinding
|
||||
|
||||
class GdgListAdapter(val clickListener: GdgClickListener): ListAdapter<GdgChapter, GdgListViewHolder>(DiffCallback){
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<GdgChapter>() {
|
||||
override fun areItemsTheSame(oldItem: GdgChapter, newItem: GdgChapter): Boolean {
|
||||
return oldItem === newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: GdgChapter, newItem: GdgChapter): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class GdgListViewHolder(private var binding: ListItemBinding):
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(listener: GdgClickListener, gdgChapter: GdgChapter) {
|
||||
binding.chapter = gdgChapter
|
||||
binding.clickListener = listener
|
||||
// This is important, because it forces the data binding to execute immediately,
|
||||
// which allows the RecyclerView to make the correct view size measurements
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(parent: ViewGroup): GdgListViewHolder {
|
||||
val layoutInflater = LayoutInflater.from(parent.context)
|
||||
val binding = ListItemBinding.inflate(layoutInflater, parent, false)
|
||||
return GdgListViewHolder(binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the RecyclerView adapter, called when RecyclerView needs a new [ViewHolder].
|
||||
*
|
||||
* A ViewHolder holds a view for the [RecyclerView] as well as providing additional information
|
||||
* to the RecyclerView such as where on the screen it was last drawn during scrolling.
|
||||
*/
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GdgListViewHolder {
|
||||
return GdgListViewHolder.from(parent)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the RecyclerView adapter, called when RecyclerView needs to show an item.
|
||||
*
|
||||
* The ViewHolder passed may be recycled, so make sure that this sets any properties that
|
||||
* may have been set previously.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: GdgListViewHolder, position: Int) {
|
||||
holder.bind(clickListener, getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
class GdgClickListener(val clickListener: (chapter: GdgChapter) -> Unit) {
|
||||
fun onClick(chapter: GdgChapter) = clickListener(chapter)
|
||||
}
|
||||
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.example.android.gdgfinder.R
|
||||
import com.example.android.gdgfinder.databinding.FragmentGdgListBinding
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
import com.google.android.gms.location.LocationRequest
|
||||
import com.google.android.gms.location.LocationResult
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
private const val LOCATION_PERMISSION_REQUEST = 1
|
||||
|
||||
private const val LOCATION_PERMISSION = "android.permission.ACCESS_FINE_LOCATION"
|
||||
|
||||
class GdgListFragment : Fragment() {
|
||||
|
||||
|
||||
private val viewModel: GdgListViewModel by lazy {
|
||||
ViewModelProviders.of(this).get(GdgListViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
val binding = FragmentGdgListBinding.inflate(inflater)
|
||||
|
||||
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
|
||||
binding.setLifecycleOwner(this)
|
||||
|
||||
// Giving the binding access to the OverviewViewModel
|
||||
binding.viewModel = viewModel
|
||||
|
||||
val adapter = GdgListAdapter(GdgClickListener { chapter ->
|
||||
val destination = Uri.parse(chapter.website)
|
||||
startActivity(Intent(Intent.ACTION_VIEW, destination))
|
||||
})
|
||||
|
||||
// Sets the adapter of the RecyclerView
|
||||
binding.gdgChapterList.adapter = adapter
|
||||
|
||||
viewModel.showNeedLocation.observe(viewLifecycleOwner, object : Observer<Boolean> {
|
||||
override fun onChanged(show: Boolean?) {
|
||||
// Snackbar is like Toast but it lets us show forever
|
||||
if (show == true) {
|
||||
Snackbar.make(
|
||||
binding.root,
|
||||
"No location. Enable location in settings (hint: test with Maps) then check app permissions!",
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.regionList.observe(viewLifecycleOwner, object : Observer<List<String>> {
|
||||
override fun onChanged(data: List<String>?) {
|
||||
data ?: return
|
||||
val chipGroup = binding.regionList
|
||||
val inflator = LayoutInflater.from(chipGroup.context)
|
||||
val children = data.map { regionName ->
|
||||
val chip = inflator.inflate(R.layout.region, chipGroup, false) as Chip
|
||||
chip.text = regionName
|
||||
chip.tag = regionName
|
||||
chip.setOnCheckedChangeListener { button, isChecked ->
|
||||
viewModel.onFilterChanged(button.tag as String, isChecked)
|
||||
}
|
||||
chip
|
||||
}
|
||||
chipGroup.removeAllViews()
|
||||
|
||||
for (chip in children) {
|
||||
chipGroup.addView(chip)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
requestLastLocationOrStartLocationUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the user a dialog asking for permission to use location.
|
||||
*/
|
||||
private fun requestLocationPermission() {
|
||||
requestPermissions(arrayOf(LOCATION_PERMISSION), LOCATION_PERMISSION_REQUEST)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the last location of this device, if known, otherwise start location updates.
|
||||
*
|
||||
* The last location is cached from the last application to request location.
|
||||
*/
|
||||
private fun requestLastLocationOrStartLocationUpdates() {
|
||||
// if we don't have permission ask for it and wait until the user grants it
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), LOCATION_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocationPermission()
|
||||
return
|
||||
}
|
||||
|
||||
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
|
||||
if (location == null) {
|
||||
startLocationUpdates(fusedLocationClient)
|
||||
} else {
|
||||
viewModel.onLocationUpdated(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start location updates, this will ask the operating system to figure out the devices location.
|
||||
*/
|
||||
private fun startLocationUpdates(fusedLocationClient: FusedLocationProviderClient) {
|
||||
// if we don't have permission ask for it and wait until the user grants it
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), LOCATION_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocationPermission()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val request = LocationRequest().setPriority(LocationRequest.PRIORITY_LOW_POWER)
|
||||
val callback = object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult?) {
|
||||
val location = locationResult?.lastLocation ?: return
|
||||
viewModel.onLocationUpdated(location)
|
||||
}
|
||||
}
|
||||
fusedLocationClient.requestLocationUpdates(request, callback, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called by Android when the user responds to the permission request.
|
||||
*
|
||||
* If granted, continue with the operation that the user gave us permission to do.
|
||||
*/
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
when (requestCode) {
|
||||
LOCATION_PERMISSION_REQUEST -> {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
requestLastLocationOrStartLocationUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.location.Location
|
||||
import androidx.lifecycle.*
|
||||
import com.example.android.gdgfinder.network.GdgApi
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
class GdgListViewModel: ViewModel() {
|
||||
|
||||
private val repository = GdgChapterRepository(GdgApi.retrofitService)
|
||||
|
||||
private var filter = FilterHolder()
|
||||
|
||||
private var currentJob: Job? = null
|
||||
|
||||
private val _gdgList = MutableLiveData<List<GdgChapter>>()
|
||||
private val _regionList = MutableLiveData<List<String>>()
|
||||
private val _showNeedLocation = MutableLiveData<Boolean>()
|
||||
|
||||
// The external LiveData interface to the property is immutable, so only this class can modify
|
||||
val gdgList: LiveData< List<GdgChapter>>
|
||||
get() = _gdgList
|
||||
|
||||
val regionList: LiveData<List<String>>
|
||||
get() = _regionList
|
||||
|
||||
val showNeedLocation: LiveData<Boolean>
|
||||
get() = _showNeedLocation
|
||||
|
||||
init {
|
||||
// process the initial filter
|
||||
onQueryChanged()
|
||||
|
||||
viewModelScope.launch {
|
||||
delay(5_000)
|
||||
_showNeedLocation.value = !repository.isFullyInitialized
|
||||
}
|
||||
}
|
||||
|
||||
private fun onQueryChanged() {
|
||||
currentJob?.cancel() // if a previous query is running cancel it before starting another
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
_gdgList.value = repository.getChaptersForFilter(filter.currentValue)
|
||||
repository.getFilters().let {
|
||||
// only update the filters list if it's changed since the last time
|
||||
if (it != _regionList.value) {
|
||||
_regionList.value = it
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
_gdgList.value = listOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onLocationUpdated(location: Location) {
|
||||
viewModelScope.launch {
|
||||
repository.onLocationChanged(location)
|
||||
onQueryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFilterChanged(filter: String, isChecked: Boolean) {
|
||||
if (this.filter.update(filter, isChecked)) {
|
||||
onQueryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private class FilterHolder {
|
||||
var currentValue: String? = null
|
||||
private set
|
||||
|
||||
fun update(changedFilter: String, isChecked: Boolean): Boolean {
|
||||
if (isChecked) {
|
||||
currentValue = changedFilter
|
||||
return true
|
||||
} else if (currentValue == changedFilter) {
|
||||
currentValue = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
GDGFinderFinal/app/src/main/res/color/selected_highlight.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?attr/colorPrimaryVariant" android:state_selected="true" />
|
||||
<item android:alpha="0.18" android:color="?attr/colorOnSurface" />
|
||||
</selector>
|
||||
34
GDGFinderFinal/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Executable file
@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="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:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<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:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
BIN
GDGFinderFinal/app/src/main/res/drawable/behind.JPG
Executable file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
GDGFinderFinal/app/src/main/res/drawable/cover.jpg
Executable file
|
After Width: | Height: | Size: 304 KiB |
43
GDGFinderFinal/app/src/main/res/drawable/ic_gdg.xml
Executable file
@ -0,0 +1,43 @@
|
||||
<vector android:height="128dp"
|
||||
android:viewportHeight="128" android:viewportWidth="84.184105"
|
||||
android:width="84dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M-23.2,-14.647h107.384v152.352h-107.384z M 0,0"/>
|
||||
<path android:fillColor="#FFC107" android:pathData="M48.6,83.8l-25.6,44.2l21.9,0l14.6,-25.3l-0.3,-0.5z"/>
|
||||
<path android:fillColor="#0F9D58" android:pathData="M37.1,64l11.5,19.8l-25.6,44.2l-23,0z"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#BF360C"
|
||||
android:pathData="M44.9,127l-21.3,0l-0.6,1l21.9,0l14.6,-25.3l-0.3,-0.5z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#263238"
|
||||
android:pathData="M0.6,127l-0.6,1l23,0l0.6,-1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
|
||||
android:pathData="M0,128l0.6,0l36.7,-63.5l-0.2,-0.5z" android:strokeAlpha="0.2"/>
|
||||
<path android:pathData="M45.9,88.5l13.6,14.2l-10.9,-18.9z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:centerX="59.316597"
|
||||
android:centerY="102.3436"
|
||||
android:gradientRadius="44.2635" android:type="radial">
|
||||
<item android:color="#33BF360C" android:offset="0"/>
|
||||
<item android:color="#05BF360C" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M45.9,88.5l2.7,-4.7l-11.5,-19.8l-5.5,9.5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:centerX="59.1909"
|
||||
android:centerY="102.623795"
|
||||
android:gradientRadius="44.6228" android:type="radial">
|
||||
<item android:color="#33263238" android:offset="0"/>
|
||||
<item android:color="#05263238" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:fillColor="#4285F4" android:pathData="M0,0l59.5,102.7l11.5,-19.8l0,-0.1l-48,-82.8z"/>
|
||||
<path android:fillColor="#DB4437" android:pathData="m78.4,70c2.1,-3.7 2.1,-8.3 0,-12l-33.5,-58h-21.9l47.9,82.9z"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#1A237E"
|
||||
android:pathData="M70.7,82.4l-11.2,19.3l-58.9,-101.7l-0.6,0l59.5,102.7l11.5,-19.8l0,-0.1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
|
||||
android:pathData="m44.9,1 l33.5,58c1,1.7 1.5,3.6 1.6,5.5 0.1,-2.2 -0.4,-4.5 -1.6,-6.5l-33.5,-58L0.1,0l0.6,1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#3E2723"
|
||||
android:pathData="m71,82.8 l7.4,-12.9c1.2,-2 1.7,-4.3 1.6,-6.5 -0.1,1.9 -0.6,3.8 -1.6,5.5l-7.7,13.4z" android:strokeAlpha="0.2"/>
|
||||
</group>
|
||||
</vector>
|
||||
74
GDGFinderFinal/app/src/main/res/drawable/ic_launcher_background.xml
Executable file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path android:fillColor="#008577"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
BIN
GDGFinderFinal/app/src/main/res/drawable/logo.png
Executable file
|
After Width: | Height: | Size: 46 KiB |
BIN
GDGFinderFinal/app/src/main/res/drawable/session.jpg
Executable file
|
After Width: | Height: | Size: 8.8 MiB |
BIN
GDGFinderFinal/app/src/main/res/drawable/wtm.JPG
Executable file
|
After Width: | Height: | Size: 2.9 MiB |
7
GDGFinderFinal/app/src/main/res/font/lobster_two.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
app:fontProviderPackage="com.google.android.gms"
|
||||
app:fontProviderQuery="Lobster Two"
|
||||
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
|
||||
</font-family>
|
||||
62
GDGFinderFinal/app/src/main/res/layout/activity_main.xml
Executable file
@ -0,0 +1,62 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/hero_image"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:tint="?attr/colorOnSecondary"
|
||||
app:srcCompat="@drawable/logo" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navigation_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:menu="@menu/drawer_view" />
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</layout>
|
||||
199
GDGFinderFinal/app/src/main/res/layout/add_gdg_fragment.xml
Executable file
@ -0,0 +1,199 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.add.AddGdgViewModel" />
|
||||
</data>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".add.AddGdgFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewIntro"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_normal"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:layout_marginEnd="@dimen/spacing_normal"
|
||||
android:contentDescription="@string/add_gdg"
|
||||
android:text="@string/add_gdg"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="411dp"
|
||||
android:layout_height="230dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:contentDescription="@string/stage_image_description"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextName"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textViewIntro"
|
||||
app:srcCompat="@drawable/behind"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextName"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:hint="@string/your_name_label"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextEmail"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView">
|
||||
|
||||
</EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextEmail"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:hint="@string/email_label"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextCity"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextName"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextCity"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:hint="@string/city_label"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextCountry"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextEmail"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextCountry"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:hint="@string/country_label"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextRegion"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextCity"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextRegion"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:hint="@string/region_label"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/contentGroup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextCountry"></EditText>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/contentGroup"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||
app:layout_constraintEnd_toEndOf="@+id/EditTextRegion"
|
||||
app:layout_constraintStart_toStartOf="@+id/EditTextRegion"
|
||||
app:layout_constraintTop_toBottomOf="@id/EditTextRegion">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelTextWhy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/motivation"
|
||||
android:text="@string/motivation"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextWhy"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/contentGroup" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextWhy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:hint="@string/enter_motivation"
|
||||
android:inputType="textMultiLine"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/contentGroup"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:accessibilityLiveRegion="polite"
|
||||
android:contentDescription="@string/submit_button_description"
|
||||
android:onClick="@{() -> viewModel.onSubmitApplication()}"
|
||||
android:text="@string/submit"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/contentGroup" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
81
GDGFinderFinal/app/src/main/res/layout/fragment_gdg_list.xml
Executable file
@ -0,0 +1,81 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.search.GdgListViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.example.android.gdg.MainActivity">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/chips"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/region_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleSelection="true"
|
||||
android:padding="@dimen/spacing_normal"/>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gdg_chapter_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chips"
|
||||
app:listData="@{viewModel.gdgList}"
|
||||
tools:listitem="@layout/list_item" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/waiting_for_location_and_network"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:showOnlyWhenEmpty="@{viewModel.gdgList}"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
134
GDGFinderFinal/app/src/main/res/layout/home_fragment.xml
Executable file
@ -0,0 +1,134 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.home.HomeViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="true"
|
||||
android:paddingBottom="@dimen/spacing_normal"
|
||||
tools:context=".home.HomeFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="4:3"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/behind" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:text="@string/about_google_developer_groups"
|
||||
android:textAppearance="?attr/textAppearanceHeadline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/image" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/intro_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/gdg_description_long"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13dp"
|
||||
android:text="@string/gdgs_are"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toStartOf="@id/end_guideline"
|
||||
app:layout_constraintStart_toEndOf="@id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/intro_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/gdg_description_bullets"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/end_image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="3:1.5"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/description"
|
||||
app:srcCompat="@drawable/wtm" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/start_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="@dimen/spacing_normal" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/end_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_end="@dimen/spacing_normal" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/spacing_normal"
|
||||
android:onClick="@{() -> viewModel.onFabClicked()}"
|
||||
app:srcCompat="@drawable/ic_gdg" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
68
GDGFinderFinal/app/src/main/res/layout/list_item.xml
Executable file
@ -0,0 +1,68 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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"
|
||||
android:id="@+id/layout">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="chapter"
|
||||
type="com.example.android.gdgfinder.network.GdgChapter" />
|
||||
|
||||
<variable
|
||||
name="clickListener"
|
||||
type="com.example.android.gdgfinder.search.GdgClickListener" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> clickListener.onClick(chapter)}"
|
||||
tools:context="com.example.android.gdg.gdglist.GdgListFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gdg_image"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="76dp"
|
||||
android:layout_marginStart="@dimen/spacing_normal"
|
||||
android:layout_marginTop="26dp"
|
||||
android:layout_marginBottom="26dp"
|
||||
android:contentDescription="TODO"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_gdg" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/spacing_normal"
|
||||
android:layout_marginEnd="@dimen/spacing_normal"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@{chapter.name}"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/gdg_image"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/gdg_image"
|
||||
app:layout_constraintTop_toTopOf="@+id/gdg_image"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="GDG Mountain View is really long so it will wrap in tools" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
25
GDGFinderFinal/app/src/main/res/layout/region.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<com.google.android.material.chip.Chip 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"
|
||||
style="@style/Widget.MaterialComponents.Chip.Choice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:checkedIconVisible="true"
|
||||
app:chipBackgroundColor="@color/selected_highlight"
|
||||
tools:checked="true" />
|
||||
22
GDGFinderFinal/app/src/main/res/menu/drawer_view.xml
Executable file
@ -0,0 +1,22 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:title="@string/gdg_search" android:id="@+id/gdg_search"
|
||||
/>
|
||||
<item android:title="@string/gdg_apply" android:id="@+id/gdg_apply"/>
|
||||
</menu>
|
||||
5
GDGFinderFinal/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Executable file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
5
GDGFinderFinal/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Executable file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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
GDGFinderFinal/app/src/main/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-mdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
GDGFinderFinal/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
31
GDGFinderFinal/app/src/main/res/navigation/nav_graph.xml
Executable file
@ -0,0 +1,31 @@
|
||||
<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/home">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/gdg_search"
|
||||
android:name="com.example.android.gdgfinder.search.GdgListFragment"
|
||||
android:label="GDG Search"
|
||||
>
|
||||
</fragment>
|
||||
<fragment android:id="@+id/home" android:name="com.example.android.gdgfinder.home.HomeFragment"
|
||||
android:label="Home" tools:layout="@layout/home_fragment">
|
||||
<action android:id="@+id/action_homeFragment_to_gdgListFragment" app:destination="@id/gdg_search"/>
|
||||
<action android:id="@+id/action_homeFragment_to_addGdgFragment" app:destination="@id/gdg_apply"/>
|
||||
</fragment>
|
||||
<fragment android:id="@+id/gdg_apply" android:name="com.example.android.gdgfinder.add.AddGdgFragment"
|
||||
android:label="Apply" tools:layout="@layout/add_gdg_fragment"/>
|
||||
|
||||
<!--<fragment-->
|
||||
<!--android:id="@+id/gdgDetailFragment"-->
|
||||
<!--android:name="com.example.android.gdg.gdgdetail.GdgDetailFragment"-->
|
||||
<!--android:label="fragment_overview"-->
|
||||
<!--tools:layout="@layout/fragment_gdg_detail" >-->
|
||||
<!--<argument-->
|
||||
<!--android:name="selectedChapter"-->
|
||||
<!--app:argType="com.example.android.gdg.network.GdgChapter"/>-->
|
||||
<!--<action android:id="@+id/action_gdgDetailFragment_to_gdgListFragment" app:destination="@id/gdgListFragment"/>-->
|
||||
<!--</fragment>-->
|
||||
</navigation>
|
||||
57
GDGFinderFinal/app/src/main/res/values-es/strings.xml
Executable file
@ -0,0 +1,57 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<string name="add_gdg">¿No pudo encontrar un GDG en su región? Genial, adelante y empieza uno. ¡Rellene el siguiente
|
||||
formulario y espere tener noticias de nosotros en breve!
|
||||
</string>
|
||||
<string name="application_submitted">Tienes tu solicitud de solicitud !! Oh bueno, en realidad no. Este es un
|
||||
formulario de prueba sólo para fines educativos. Si realmente desea enviar su solicitud, visite el sitio web!
|
||||
</string>
|
||||
<string name="gdg_search">Búsqueda GDG</string>
|
||||
<string name="gdg_apply">Aplicar para ejecutar GDG</string>
|
||||
<string name="gdgs_are">Los GDG son…</string>
|
||||
<string name="gdg_description_long">Los GDG son grupos locales de desarrolladores que están específicamente
|
||||
interesados en los productos y API de Google. Cada grupo local se denomina capítulo GDG y puede albergar una
|
||||
variedad de actividades técnicas para desarrolladores, desde solo unas pocas personas que se reúnen para ver
|
||||
nuestros últimos videos, grandes reuniones con demostraciones y charlas tecnológicas, o hackathons. Google
|
||||
Developers admite y reconoce los capítulos de GDG, pero no los posee ni los administra.
|
||||
</string>
|
||||
<string name="about_google_developer_groups">Acerca de los Grupos de Desarrolladores de Google</string>
|
||||
<string name="gdg_description_bullets">️Corrido por personas apasionadas en comunidades de desarrolladores locales
|
||||
️Un grupo para compartir ideas y establecer contactos con otros desarrolladores. Un lugar para aprender sobre
|
||||
las tecnologías de Google y las herramientas de desarrollo. Abierto y gratuito para todos los interesados.
|
||||
</string>
|
||||
<string name="waiting_for_location_and_network">Esperando localización y resultado de red. Si se ejecuta en un
|
||||
emulador, abra la aplicación Mapas para habilitar los servicios de ubicación.
|
||||
</string>
|
||||
<string name="hello_blank_fragment">Hola fragmento en blanco</string>
|
||||
<string name="app_name">GDG</string>
|
||||
<string name="stage_image_description">Foto desde el escenario de un devfest.</string>
|
||||
<string name="submit_button_description">Botón para enviar formulario</string>
|
||||
<string name="form_name_description">introduzca su nombre</string>
|
||||
<string name="motivation">Cuéntanos un poco más acerca de por qué quieres comenzar un GDG</string>
|
||||
<string name="your_name_label">Tu nombre</string>
|
||||
<string name="email_label">e-mail</string>
|
||||
<string name="city_label">ciudad</string>
|
||||
<string name="country_label">país</string>
|
||||
<string name="region_label">región</string>
|
||||
<string name="enter_motivation">Por favor ingrese su motivación en 1–2 oraciones</string>
|
||||
<string name="submitted">Enviado, no hay necesidad de enviar de nuevo</string>
|
||||
<string name="done">Hecho</string>
|
||||
<string name="submit">Enviar</string>
|
||||
</resources>
|
||||
26
GDGFinderFinal/app/src/main/res/values-night/colors.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="primaryColor">#1a237e</color>
|
||||
<color name="primaryLightColor">#534bae</color>
|
||||
<color name="primaryDarkColor">#000051</color>
|
||||
<color name="secondaryColor">#5e35b1</color>
|
||||
<color name="secondaryLightColor">#9162e4</color>
|
||||
<color name="secondaryDarkColor">#280680</color>
|
||||
<color name="primaryTextColor">#ffffff</color>
|
||||
<color name="secondaryTextColor">#ffffff</color>
|
||||
</resources>
|
||||
26
GDGFinderFinal/app/src/main/res/values/colors.xml
Executable file
@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="primaryColor">#669df6</color>
|
||||
<color name="primaryLightColor">#9cceff</color>
|
||||
<color name="primaryDarkColor">#266fc3</color>
|
||||
<color name="secondaryColor">#a142f4</color>
|
||||
<color name="secondaryLightColor">#d774ff</color>
|
||||
<color name="secondaryDarkColor">#6b00c0</color>
|
||||
<color name="primaryTextColor">#000000</color>
|
||||
<color name="secondaryTextColor">#ffffff</color>
|
||||
</resources>
|
||||
20
GDGFinderFinal/app/src/main/res/values/dimens.xml
Executable file
@ -0,0 +1,20 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<dimen name="spacing_normal">16dp</dimen>
|
||||
</resources>
|
||||
17
GDGFinderFinal/app/src/main/res/values/font_certs.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<array name="com_google_android_gms_fonts_certs">
|
||||
<item>@array/com_google_android_gms_fonts_certs_dev</item>
|
||||
<item>@array/com_google_android_gms_fonts_certs_prod</item>
|
||||
</array>
|
||||
<string-array name="com_google_android_gms_fonts_certs_dev">
|
||||
<item>
|
||||
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
|
||||
</item>
|
||||
</string-array>
|
||||
<string-array name="com_google_android_gms_fonts_certs_prod">
|
||||
<item>
|
||||
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
|
||||
</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<array name="preloaded_fonts" translatable="false">
|
||||
<item>@font/lobster_two</item>
|
||||
</array>
|
||||
</resources>
|
||||
59
GDGFinderFinal/app/src/main/res/values/strings.xml
Executable file
@ -0,0 +1,59 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">GDG</string>
|
||||
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="add_gdg">"Couldn't find a GDG in your region? Great, go ahead and start one. Fill the form below and
|
||||
expect to hear from us shortly! "
|
||||
</string>
|
||||
<string name="application_submitted">Got your application request!! Oh well, not really. This is a test form just
|
||||
for educational purposes. If you really want to submit your application please visit the website!
|
||||
</string>
|
||||
<string name="gdg_search">GDG Search</string>
|
||||
<string name="gdg_apply">Apply to run GDG</string>
|
||||
<string name="gdgs_are">GDGs Are…</string>
|
||||
<string name="gdg_description_long">GDGs are local groups of developers who are specifically interested in Google
|
||||
products and APIs. Each local group is called a GDG chapter and can host a variety of technical activities for
|
||||
developers - from just a few people getting together to watch our latest videos, to large gatherings with demos
|
||||
and tech talks, to hackathons. Google Developers supports and recognizes GDG chapters, but doesn’t own or manage
|
||||
them.
|
||||
</string>
|
||||
<string name="about_google_developer_groups">About Google Developer Groups</string>
|
||||
<string name="gdg_description_bullets">️Run by passionate individuals in local developer communities\n
|
||||
️A group to share ideas and network with fellow developers \n
|
||||
️A place to learn about Google technologies and development tools \n
|
||||
Open to and free for all interested
|
||||
</string>
|
||||
<string name="waiting_for_location_and_network">Waiting for location and network result.\n\n If running on an
|
||||
emulator open the Maps app to enable location services.
|
||||
</string>
|
||||
|
||||
<string name="stage_image_description">Photo from stage from a devfest</string>
|
||||
<string name="submit_button_description">Button to submit form</string>
|
||||
<string name="form_name_description">enter your name</string>
|
||||
<string name="motivation">Tell us a bit more about why do you want to start a GDG</string>
|
||||
<string name="your_name_label">Your name</string>
|
||||
<string name="email_label">e-mail</string>
|
||||
<string name="city_label">city</string>
|
||||
<string name="country_label">country</string>
|
||||
<string name="region_label">region</string>
|
||||
<string name="enter_motivation">Please enter your motivation in 1-2 sentences</string>
|
||||
<string name="submitted">Submitted, no need to submit again</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="submit">Submit</string>
|
||||
</resources>
|
||||
38
GDGFinderFinal/app/src/main/res/values/styles.xml
Executable file
@ -0,0 +1,38 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/primaryColor</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
|
||||
<item name="colorPrimaryVariant">@color/primaryLightColor</item>
|
||||
<item name="colorOnPrimary">@color/primaryTextColor</item>
|
||||
<item name="colorSecondary">@color/secondaryColor</item>
|
||||
<item name="colorSecondaryVariant">@color/secondaryDarkColor</item>
|
||||
<item name="colorOnSecondary">@color/secondaryTextColor</item>
|
||||
<item name="android:fontFamily">@font/lobster_two</item>
|
||||
<item name="fontFamily">@font/lobster_two</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.CustomHeadline6" parent="TextAppearance.MaterialComponents.Headline6">
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="textAppearanceHeadline6">@style/TextAppearance.CustomHeadline6</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
44
GDGFinderFinal/build.gradle
Executable file
@ -0,0 +1,44 @@
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
// Versions for all the dependencies we plan to use. It's particularly useful for kotlin and
|
||||
// navigation where the versions of the plugin needs to be the same as the version of the
|
||||
// library defined in the app Gradle file
|
||||
version_android_gradle_plugin = "3.2.0"
|
||||
version_core = "1.0.1"
|
||||
version_constraint_layout = "1.1.3"
|
||||
version_glide = "4.8.0"
|
||||
version_kotlin = "1.3.21"
|
||||
version_kotlin_coroutines = "1.1.0"
|
||||
version_lifecycle_extensions = "2.0.0"
|
||||
version_moshi = "1.8.0"
|
||||
version_navigation = "1.0.0"
|
||||
version_retrofit = "2.5.0"
|
||||
version_retrofit_coroutines_adapter = "0.9.2"
|
||||
version_recyclerview = "1.1.0-alpha03"
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin"
|
||||
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$version_navigation"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
21
GDGFinderFinal/gradle.properties
Executable file
@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
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
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
BIN
GDGFinderFinal/gradle/wrapper/gradle-wrapper.jar
vendored
Executable file
6
GDGFinderFinal/gradle/wrapper/gradle-wrapper.properties
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
#Tue Jul 09 17:17:52 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
GDGFinderFinal/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
GDGFinderFinal/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
|
||||
1
GDGFinderFinal/settings.gradle
Executable file
@ -0,0 +1 @@
|
||||
include ':app'
|
||||
201
GDGFinderMaterial/LICENSE
Executable file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
1
GDGFinderMaterial/README.md
Executable file
@ -0,0 +1 @@
|
||||
# andfun-kotlin-gdg-finder
|
||||
1
GDGFinderMaterial/app/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/build
|
||||
96
GDGFinderMaterial/app/build.gradle
Executable file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2018, 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.gdgfinder"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
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"
|
||||
// use viewModelScope from lifecycle-viewmodel-ktx
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-alpha03"
|
||||
|
||||
// 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
|
||||
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"
|
||||
|
||||
// material design components
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
|
||||
// Client for retrieving location
|
||||
implementation "com.google.android.gms:play-services-location:16.0.0"
|
||||
}
|
||||
21
GDGFinderMaterial/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
|
||||
30
GDGFinderMaterial/app/src/main/AndroidManifest.xml
Executable file
@ -0,0 +1,30 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" package="com.example.android.gdgfinder">
|
||||
|
||||
<!-- In order for our app to access the Internet, we need to define this permission. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- To access the location we need to ask for permission -->
|
||||
<!-- see https://developer.android.com/training/location/retrieve-current.html -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
android:resource="@array/preloaded_fonts" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.gdgfinder
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.search.GdgListAdapter
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
||||
/**
|
||||
* When there is no Mars property data (data is null), hide the [RecyclerView], otherwise show it.
|
||||
*/
|
||||
@BindingAdapter("listData")
|
||||
fun bindRecyclerView(recyclerView: RecyclerView, data: List<GdgChapter>?) {
|
||||
val adapter = recyclerView.adapter as GdgListAdapter
|
||||
adapter.submitList(data) {
|
||||
// scroll the list to the top after the diffs are calculated and posted
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("showOnlyWhenEmpty")
|
||||
fun View.showOnlyWhenEmpty(data: List<GdgChapter>?) {
|
||||
visibility = when {
|
||||
data == null || data.isEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.gdgfinder
|
||||
|
||||
import android.os.Bundle
|
||||
import android.transition.TransitionManager
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.ui.NavigationUI.navigateUp
|
||||
import androidx.navigation.ui.NavigationUI.setupWithNavController
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.example.android.gdgfinder.databinding.ActivityMainBinding
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
||||
|
||||
setupNavigation()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the hamburger menu or back button are pressed on the Toolbar
|
||||
*
|
||||
* Delegate this to Navigation.
|
||||
*/
|
||||
override fun onSupportNavigateUp() = navigateUp(findNavController(R.id.nav_host_fragment), binding.drawerLayout)
|
||||
|
||||
/**
|
||||
* Setup Navigation for this Activity
|
||||
*/
|
||||
private fun setupNavigation() {
|
||||
// first find the nav controller
|
||||
val navController = findNavController(R.id.nav_host_fragment)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
// then setup the action bar, tell it about the DrawerLayout
|
||||
setupActionBarWithNavController(navController, binding.drawerLayout)
|
||||
|
||||
|
||||
// finally setup the left drawer (called a NavigationView)
|
||||
binding.navigationView.setupWithNavController(navController)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination: NavDestination, _ ->
|
||||
val toolBar = supportActionBar ?: return@addOnDestinationChangedListener
|
||||
when(destination.id) {
|
||||
R.id.home -> {
|
||||
toolBar.setDisplayShowTitleEnabled(false)
|
||||
binding.heroImage.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
toolBar.setDisplayShowTitleEnabled(true)
|
||||
binding.heroImage.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.gdgfinder.add
|
||||
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Observer
|
||||
|
||||
import com.example.android.gdgfinder.R
|
||||
import com.example.android.gdgfinder.databinding.AddGdgFragmentBinding
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
class AddGdgFragment : Fragment() {
|
||||
|
||||
private val viewModel: AddGdgViewModel by lazy {
|
||||
ViewModelProviders.of(this).get(AddGdgViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
val binding = AddGdgFragmentBinding.inflate(inflater)
|
||||
|
||||
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
|
||||
binding.setLifecycleOwner(this)
|
||||
|
||||
binding.viewModel = viewModel
|
||||
|
||||
viewModel.showSnackBarEvent.observe(this, Observer {
|
||||
if (it == true) { // Observed state is true.
|
||||
Snackbar.make(
|
||||
activity!!.findViewById(android.R.id.content),
|
||||
getString(R.string.application_submitted),
|
||||
Snackbar.LENGTH_SHORT // How long to display the message.
|
||||
).show()
|
||||
viewModel.doneShowingSnackbar()
|
||||
}
|
||||
})
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.gdgfinder.add
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
class AddGdgViewModel : ViewModel() {
|
||||
|
||||
/**
|
||||
* Request a toast by setting this value to true.
|
||||
*
|
||||
* This is private because we don't want to expose setting this value to the Fragment.
|
||||
*/
|
||||
private var _showSnackbarEvent = MutableLiveData<Boolean?>()
|
||||
|
||||
/**
|
||||
* If this is true, immediately `show()` a toast and call `doneShowingSnackbar()`.
|
||||
*/
|
||||
val showSnackBarEvent: LiveData<Boolean?>
|
||||
get() = _showSnackbarEvent
|
||||
|
||||
/**
|
||||
* Call this immediately after calling `show()` on a toast.
|
||||
*
|
||||
* It will clear the toast request, so if the user rotates their phone it won't show a duplicate
|
||||
* toast.
|
||||
*/
|
||||
fun doneShowingSnackbar() {
|
||||
_showSnackbarEvent.value = null
|
||||
}
|
||||
|
||||
fun onSubmitApplication() {
|
||||
_showSnackbarEvent.value = true
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.gdgfinder.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.example.android.gdgfinder.R
|
||||
import com.example.android.gdgfinder.databinding.HomeFragmentBinding
|
||||
|
||||
class HomeFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = HomeFragment()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: HomeViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val binding = HomeFragmentBinding.inflate(inflater)
|
||||
viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
|
||||
|
||||
binding.viewModel = viewModel
|
||||
|
||||
viewModel.navigateToSearch.observe(viewLifecycleOwner,
|
||||
Observer<Boolean> { navigate ->
|
||||
if(navigate) {
|
||||
val navController = findNavController()
|
||||
navController.navigate(R.id.action_homeFragment_to_gdgListFragment)
|
||||
viewModel.onNavigatedToSearch()
|
||||
}
|
||||
})
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.gdgfinder.home
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
|
||||
private val _navigateToSearch = MutableLiveData<Boolean>()
|
||||
val navigateToSearch: LiveData<Boolean>
|
||||
get() = _navigateToSearch
|
||||
|
||||
fun onFabClicked() {
|
||||
_navigateToSearch.value = true
|
||||
}
|
||||
|
||||
fun onNavigatedToSearch() {
|
||||
_navigateToSearch.value = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.gdgfinder.network
|
||||
|
||||
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import kotlinx.coroutines.Deferred
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.GET
|
||||
|
||||
// The alternative URL is for a server with a recent snapshot. If you are having problems
|
||||
// with the given URL (app crashing), use the alternative.
|
||||
//private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com/"
|
||||
private const val BASE_URL = "https://developers.google.com/community/gdg/directory/"
|
||||
|
||||
interface GdgApiService {
|
||||
//@GET("gdg-directory.json")
|
||||
@GET("directory.json")
|
||||
|
||||
fun getChapters():
|
||||
// The Coroutine Call Adapter allows us to return a Deferred, a Job with a result
|
||||
Deferred<GdgResponse>
|
||||
}
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
private val retrofit = Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||
.baseUrl(BASE_URL)
|
||||
.build()
|
||||
|
||||
object GdgApi {
|
||||
val retrofitService: GdgApiService by lazy { retrofit.create(GdgApiService::class.java) }
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.gdgfinder.network
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
|
||||
|
||||
@Parcelize
|
||||
data class GdgChapter(
|
||||
@Json(name = "chapter_name") val name: String,
|
||||
@Json(name = "cityarea") val city: String,
|
||||
val country: String,
|
||||
val region: String,
|
||||
val website: String,
|
||||
val geo: LatLong
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class LatLong(
|
||||
val lat: Double,
|
||||
@Json(name = "lng")
|
||||
val long: Double
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class GdgResponse(
|
||||
@Json(name = "filters_") val filters: Filter,
|
||||
@Json(name = "data") val chapters: List<GdgChapter>
|
||||
): Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class Filter(
|
||||
@Json(name = "region") val regions: List<String>
|
||||
): Parcelable
|
||||
|
||||
//"chapter_name": "GDG Bordj Bou-Arréridj",
|
||||
//"cityarea": "Burj Bu Arririj",
|
||||
//"country": "Algeria",
|
||||
//"region": "Africa",
|
||||
//"website": "https://www.meetup.com/GDG-BBA",
|
||||
//"geo": {
|
||||
// "lat": 36.06000137,
|
||||
// "lng": 4.630000114
|
||||
//}
|
||||
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.location.Location
|
||||
import com.example.android.gdgfinder.network.GdgApiService
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.network.GdgResponse
|
||||
import com.example.android.gdgfinder.network.LatLong
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GdgChapterRepository(gdgApiService: GdgApiService) {
|
||||
|
||||
/**
|
||||
* A single network request, the results won't change. For this lesson we did not add an offline cache for simplicity
|
||||
* and the result will be cached in memory.
|
||||
*/
|
||||
private val request = gdgApiService.getChapters()
|
||||
|
||||
/**
|
||||
* An in-progress (or potentially completed) sort, this may be null or cancelled at any time.
|
||||
*
|
||||
* If this is non-null, calling await will get the result of the last sorting request.
|
||||
*
|
||||
* This will be cancelled whenever location changes, as the old results are no longer valid.
|
||||
*/
|
||||
private var inProgressSort: Deferred<SortedData>? = null
|
||||
|
||||
var isFullyInitialized = false
|
||||
private set
|
||||
|
||||
|
||||
/**
|
||||
* Get the chapters list for a specified filter.
|
||||
*
|
||||
* This will be cancel if a new location is sent before the result is available.
|
||||
*
|
||||
* This works by first waiting for any previously in-progress sorts, and if a sort has not yet started
|
||||
* it will start a new sort (which may happen if location is disabled on the device)
|
||||
*/
|
||||
suspend fun getChaptersForFilter(filter: String?): List<GdgChapter> {
|
||||
val data = sortedData()
|
||||
return when(filter) {
|
||||
null -> data.chapters
|
||||
else -> data.chaptersByRegion.getOrElse(filter) { emptyList() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filters sorted by distance from the last location.
|
||||
*
|
||||
* This will cancel if a new location is sent before the result is available.
|
||||
*
|
||||
* This works by first waiting for any previously in-progress sorts, and if a sort has not yet started
|
||||
* it will start a new sort (which may happen if location is disabled on the device)
|
||||
*/
|
||||
suspend fun getFilters(): List<String> = sortedData().filters
|
||||
|
||||
/**
|
||||
* Get the computed sorted data after it completes, or start a new sort if none are running.
|
||||
*
|
||||
* This will always cancel if the location changes while the sort is in progress.
|
||||
*/
|
||||
private suspend fun sortedData(): SortedData = withContext(Dispatchers.Main) {
|
||||
// We need to ensure we're on Dispatchers.Main so that this is not running on multiple Dispatchers and we
|
||||
// modify the member inProgressSort.
|
||||
|
||||
// Since this was called from viewModelScope, that will always be a simple if check (not expensive), but
|
||||
// by specifying the dispatcher we can protect against incorrect usage.
|
||||
|
||||
// if there's currently a sort running (or completed) wait for it to complete and return that value
|
||||
// otherwise, start a new sort with no location (the user has likely not given us permission to use location
|
||||
// yet)
|
||||
inProgressSort?.await() ?: doSortData()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to force a new sort to start.
|
||||
*
|
||||
* This will start a new coroutine to perform the sort. Future requests to sorted data can use the deferred in
|
||||
* [inProgressSort] to get the result of the last sort without sorting the data again. This guards against multiple
|
||||
* sorts being performed on the same data, which is inefficient.
|
||||
*
|
||||
* This will always cancel if the location changes while the sort is in progress.
|
||||
*
|
||||
* @return the result of the started sort
|
||||
*/
|
||||
private suspend fun doSortData(location: Location? = null): SortedData {
|
||||
// since we'll need to launch a new coroutine for the sorting use coroutineScope.
|
||||
// coroutineScope will automatically wait for anything started via async {} or await{} in it's block to
|
||||
// complete.
|
||||
val result = coroutineScope {
|
||||
// launch a new coroutine to do the sort (so other requests can wait for this sort to complete)
|
||||
val deferred = async { SortedData.from(request.await(), location) }
|
||||
// cache the Deferred so any future requests can wait for this sort
|
||||
inProgressSort = deferred
|
||||
// and return the result of this sort
|
||||
deferred.await()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when location changes.
|
||||
*
|
||||
* This will cancel any previous queries, so it's important to re-request the data after calling this function.
|
||||
*
|
||||
* @param location the location to sort by
|
||||
*/
|
||||
suspend fun onLocationChanged(location: Location) {
|
||||
// We need to ensure we're on Dispatchers.Main so that this is not running on multiple Dispatchers and we
|
||||
// modify the member inProgressSort.
|
||||
|
||||
// Since this was called from viewModelScope, that will always be a simple if check (not expensive), but
|
||||
// by specifying the dispatcher we can protect against incorrect usage.
|
||||
withContext(Dispatchers.Main) {
|
||||
isFullyInitialized = true
|
||||
|
||||
// cancel any in progress sorts, their result is not valid anymore.
|
||||
inProgressSort?.cancel()
|
||||
|
||||
doSortData(location)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds data sorted by the distance from the last location.
|
||||
*
|
||||
* Note, by convention this class won't sort on the Main thread. This is not a public API and should
|
||||
* only be called by [doSortData].
|
||||
*/
|
||||
private class SortedData private constructor(
|
||||
val chapters: List<GdgChapter>,
|
||||
val filters: List<String>,
|
||||
val chaptersByRegion: Map<String, List<GdgChapter>>
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Sort the data from a [GdgResponse] by the specified location.
|
||||
*
|
||||
* @param response the response to sort
|
||||
* @param location the location to sort by, if null the data will not be sorted.
|
||||
*/
|
||||
suspend fun from(response: GdgResponse, location: Location?): SortedData {
|
||||
return withContext(Dispatchers.Default) {
|
||||
// this sorting is too expensive to do on the main thread, so do thread confinement here.
|
||||
val chapters: List<GdgChapter> = response.chapters.sortByDistanceFrom(location)
|
||||
// use distinctBy which will maintain the input order - this will have the effect of making
|
||||
// a filter list sorted by the distance from the current location
|
||||
val filters: List<String> = chapters.map { it.region } .distinctBy { it }
|
||||
// group the chapters by region so that filter queries don't require any work
|
||||
val chaptersByRegion: Map<String, List<GdgChapter>> = chapters.groupBy { it.region }
|
||||
// return the sorted result
|
||||
SortedData(chapters, filters, chaptersByRegion)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort a list of GdgChapter by their distance from the specified location.
|
||||
*
|
||||
* @param currentLocation returned list will be sorted by the distance, or unsorted if null
|
||||
*/
|
||||
private fun List<GdgChapter>.sortByDistanceFrom(currentLocation: Location?): List<GdgChapter> {
|
||||
currentLocation ?: return this
|
||||
|
||||
return sortedBy { distanceBetween(it.geo, currentLocation)}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance (in meters) between a LatLong and a Location.
|
||||
*/
|
||||
private fun distanceBetween(start: LatLong, currentLocation: Location): Float {
|
||||
val results = FloatArray(3)
|
||||
Location.distanceBetween(start.lat, start.long, currentLocation.latitude, currentLocation.longitude, results)
|
||||
return results[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import com.example.android.gdgfinder.search.GdgListAdapter.GdgListViewHolder
|
||||
import com.example.android.gdgfinder.databinding.ListItemBinding
|
||||
|
||||
class GdgListAdapter(val clickListener: GdgClickListener): ListAdapter<GdgChapter, GdgListViewHolder>(DiffCallback){
|
||||
companion object DiffCallback : DiffUtil.ItemCallback<GdgChapter>() {
|
||||
override fun areItemsTheSame(oldItem: GdgChapter, newItem: GdgChapter): Boolean {
|
||||
return oldItem === newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: GdgChapter, newItem: GdgChapter): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
class GdgListViewHolder(private var binding: ListItemBinding):
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(listener: GdgClickListener, gdgChapter: GdgChapter) {
|
||||
binding.chapter = gdgChapter
|
||||
binding.clickListener = listener
|
||||
// This is important, because it forces the data binding to execute immediately,
|
||||
// which allows the RecyclerView to make the correct view size measurements
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(parent: ViewGroup): GdgListViewHolder {
|
||||
val layoutInflater = LayoutInflater.from(parent.context)
|
||||
val binding = ListItemBinding.inflate(layoutInflater, parent, false)
|
||||
return GdgListViewHolder(binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the RecyclerView adapter, called when RecyclerView needs a new [ViewHolder].
|
||||
*
|
||||
* A ViewHolder holds a view for the [RecyclerView] as well as providing additional information
|
||||
* to the RecyclerView such as where on the screen it was last drawn during scrolling.
|
||||
*/
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GdgListViewHolder {
|
||||
return GdgListViewHolder.from(parent)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of the RecyclerView adapter, called when RecyclerView needs to show an item.
|
||||
*
|
||||
* The ViewHolder passed may be recycled, so make sure that this sets any properties that
|
||||
* may have been set previously.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: GdgListViewHolder, position: Int) {
|
||||
holder.bind(clickListener, getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
class GdgClickListener(val clickListener: (chapter: GdgChapter) -> Unit) {
|
||||
fun onClick(chapter: GdgChapter) = clickListener(chapter)
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.example.android.gdgfinder.databinding.FragmentGdgListBinding
|
||||
import com.google.android.gms.location.*
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.example.android.gdgfinder.R
|
||||
|
||||
private const val LOCATION_PERMISSION_REQUEST = 1
|
||||
|
||||
private const val LOCATION_PERMISSION = "android.permission.ACCESS_FINE_LOCATION"
|
||||
|
||||
class GdgListFragment : Fragment() {
|
||||
|
||||
|
||||
private val viewModel: GdgListViewModel by lazy {
|
||||
ViewModelProviders.of(this).get(GdgListViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
val binding = FragmentGdgListBinding.inflate(inflater)
|
||||
|
||||
// Allows Data Binding to Observe LiveData with the lifecycle of this Fragment
|
||||
binding.setLifecycleOwner(this)
|
||||
|
||||
// Giving the binding access to the OverviewViewModel
|
||||
binding.viewModel = viewModel
|
||||
|
||||
val adapter = GdgListAdapter(GdgClickListener { chapter ->
|
||||
val destination = Uri.parse(chapter.website)
|
||||
startActivity(Intent(Intent.ACTION_VIEW, destination))
|
||||
})
|
||||
|
||||
// Sets the adapter of the RecyclerView
|
||||
binding.gdgChapterList.adapter = adapter
|
||||
|
||||
viewModel.showNeedLocation.observe(viewLifecycleOwner, object: Observer<Boolean> {
|
||||
override fun onChanged(show: Boolean?) {
|
||||
// Snackbar is like Toast but it lets us show forever
|
||||
if (show == true) {
|
||||
Snackbar.make(
|
||||
binding.root,
|
||||
"No location. Enable location in settings (hint: test with Maps) then check app permissions!",
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
requestLastLocationOrStartLocationUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the user a dialog asking for permission to use location.
|
||||
*/
|
||||
private fun requestLocationPermission() {
|
||||
requestPermissions(arrayOf(LOCATION_PERMISSION), LOCATION_PERMISSION_REQUEST)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the last location of this device, if known, otherwise start location updates.
|
||||
*
|
||||
* The last location is cached from the last application to request location.
|
||||
*/
|
||||
private fun requestLastLocationOrStartLocationUpdates() {
|
||||
// if we don't have permission ask for it and wait until the user grants it
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), LOCATION_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocationPermission()
|
||||
return
|
||||
}
|
||||
|
||||
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
|
||||
if (location == null) {
|
||||
startLocationUpdates(fusedLocationClient)
|
||||
} else {
|
||||
viewModel.onLocationUpdated(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start location updates, this will ask the operating system to figure out the devices location.
|
||||
*/
|
||||
private fun startLocationUpdates(fusedLocationClient: FusedLocationProviderClient) {
|
||||
// if we don't have permission ask for it and wait until the user grants it
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), LOCATION_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocationPermission()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val request = LocationRequest().setPriority(LocationRequest.PRIORITY_LOW_POWER)
|
||||
val callback = object: LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult?) {
|
||||
val location = locationResult?.lastLocation ?: return
|
||||
viewModel.onLocationUpdated(location)
|
||||
}
|
||||
}
|
||||
fusedLocationClient.requestLocationUpdates(request, callback, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called by Android when the user responds to the permission request.
|
||||
*
|
||||
* If granted, continue with the operation that the user gave us permission to do.
|
||||
*/
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
when(requestCode) {
|
||||
LOCATION_PERMISSION_REQUEST -> {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
requestLastLocationOrStartLocationUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.gdgfinder.search
|
||||
|
||||
import android.location.Location
|
||||
import androidx.lifecycle.*
|
||||
import com.example.android.gdgfinder.network.GdgApi
|
||||
import com.example.android.gdgfinder.network.GdgChapter
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
class GdgListViewModel: ViewModel() {
|
||||
|
||||
private val repository = GdgChapterRepository(GdgApi.retrofitService)
|
||||
|
||||
private var filter = FilterHolder()
|
||||
|
||||
private var currentJob: Job? = null
|
||||
|
||||
private val _gdgList = MutableLiveData<List<GdgChapter>>()
|
||||
private val _regionList = MutableLiveData<List<String>>()
|
||||
private val _showNeedLocation = MutableLiveData<Boolean>()
|
||||
|
||||
// The external LiveData interface to the property is immutable, so only this class can modify
|
||||
val gdgList: LiveData< List<GdgChapter>>
|
||||
get() = _gdgList
|
||||
|
||||
val regionList: LiveData<List<String>>
|
||||
get() = _regionList
|
||||
|
||||
val showNeedLocation: LiveData<Boolean>
|
||||
get() = _showNeedLocation
|
||||
|
||||
init {
|
||||
// process the initial filter
|
||||
onQueryChanged()
|
||||
|
||||
viewModelScope.launch {
|
||||
delay(5_000)
|
||||
_showNeedLocation.value = !repository.isFullyInitialized
|
||||
}
|
||||
}
|
||||
|
||||
private fun onQueryChanged() {
|
||||
currentJob?.cancel() // if a previous query is running cancel it before starting another
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
_gdgList.value = repository.getChaptersForFilter(filter.currentValue)
|
||||
repository.getFilters().let {
|
||||
// only update the filters list if it's changed since the last time
|
||||
if (it != _regionList.value) {
|
||||
_regionList.value = it
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
_gdgList.value = listOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onLocationUpdated(location: Location) {
|
||||
viewModelScope.launch {
|
||||
repository.onLocationChanged(location)
|
||||
onQueryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFilterChanged(filter: String, isChecked: Boolean) {
|
||||
if (this.filter.update(filter, isChecked)) {
|
||||
onQueryChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private class FilterHolder {
|
||||
var currentValue: String? = null
|
||||
private set
|
||||
|
||||
fun update(changedFilter: String, isChecked: Boolean): Boolean {
|
||||
if (isChecked) {
|
||||
currentValue = changedFilter
|
||||
return true
|
||||
} else if (currentValue == changedFilter) {
|
||||
currentValue = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
GDGFinderMaterial/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Executable file
@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="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:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<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:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
BIN
GDGFinderMaterial/app/src/main/res/drawable/behind.JPG
Executable file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
GDGFinderMaterial/app/src/main/res/drawable/cover.jpg
Executable file
|
After Width: | Height: | Size: 304 KiB |
43
GDGFinderMaterial/app/src/main/res/drawable/ic_gdg.xml
Executable file
@ -0,0 +1,43 @@
|
||||
<vector android:autoMirrored="true" android:height="128dp"
|
||||
android:viewportHeight="128" android:viewportWidth="84.184105"
|
||||
android:width="84dp" xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group>
|
||||
<clip-path android:pathData="M-23.2,-14.647h107.384v152.352h-107.384z M 0,0"/>
|
||||
<path android:fillColor="#FFC107" android:pathData="M48.6,83.8l-25.6,44.2l21.9,0l14.6,-25.3l-0.3,-0.5z"/>
|
||||
<path android:fillColor="#0F9D58" android:pathData="M37.1,64l11.5,19.8l-25.6,44.2l-23,0z"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#BF360C"
|
||||
android:pathData="M44.9,127l-21.3,0l-0.6,1l21.9,0l14.6,-25.3l-0.3,-0.5z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#263238"
|
||||
android:pathData="M0.6,127l-0.6,1l23,0l0.6,-1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
|
||||
android:pathData="M0,128l0.6,0l36.7,-63.5l-0.2,-0.5z" android:strokeAlpha="0.2"/>
|
||||
<path android:pathData="M45.9,88.5l13.6,14.2l-10.9,-18.9z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:centerX="59.316597"
|
||||
android:centerY="102.3436"
|
||||
android:gradientRadius="44.2635" android:type="radial">
|
||||
<item android:color="#33BF360C" android:offset="0"/>
|
||||
<item android:color="#05BF360C" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:pathData="M45.9,88.5l2.7,-4.7l-11.5,-19.8l-5.5,9.5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient android:centerX="59.1909"
|
||||
android:centerY="102.623795"
|
||||
android:gradientRadius="44.6228" android:type="radial">
|
||||
<item android:color="#33263238" android:offset="0"/>
|
||||
<item android:color="#05263238" android:offset="1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path android:fillColor="#4285F4" android:pathData="M0,0l59.5,102.7l11.5,-19.8l0,-0.1l-48,-82.8z"/>
|
||||
<path android:fillColor="#DB4437" android:pathData="m78.4,70c2.1,-3.7 2.1,-8.3 0,-12l-33.5,-58h-21.9l47.9,82.9z"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#1A237E"
|
||||
android:pathData="M70.7,82.4l-11.2,19.3l-58.9,-101.7l-0.6,0l59.5,102.7l11.5,-19.8l0,-0.1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
|
||||
android:pathData="m44.9,1 l33.5,58c1,1.7 1.5,3.6 1.6,5.5 0.1,-2.2 -0.4,-4.5 -1.6,-6.5l-33.5,-58L0.1,0l0.6,1z" android:strokeAlpha="0.2"/>
|
||||
<path android:fillAlpha="0.2" android:fillColor="#3E2723"
|
||||
android:pathData="m71,82.8 l7.4,-12.9c1.2,-2 1.7,-4.3 1.6,-6.5 -0.1,1.9 -0.6,3.8 -1.6,5.5l-7.7,13.4z" android:strokeAlpha="0.2"/>
|
||||
</group>
|
||||
</vector>
|
||||
74
GDGFinderMaterial/app/src/main/res/drawable/ic_launcher_background.xml
Executable file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path android:fillColor="#008577"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
BIN
GDGFinderMaterial/app/src/main/res/drawable/logo.png
Executable file
|
After Width: | Height: | Size: 46 KiB |
BIN
GDGFinderMaterial/app/src/main/res/drawable/session.jpg
Executable file
|
After Width: | Height: | Size: 8.8 MiB |
BIN
GDGFinderMaterial/app/src/main/res/drawable/wtm.JPG
Executable file
|
After Width: | Height: | Size: 2.9 MiB |
7
GDGFinderMaterial/app/src/main/res/font/lobster_two.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:fontProviderAuthority="com.google.android.gms.fonts"
|
||||
app:fontProviderPackage="com.google.android.gms"
|
||||
app:fontProviderQuery="Lobster Two"
|
||||
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
|
||||
</font-family>
|
||||
62
GDGFinderMaterial/app/src/main/res/layout/activity_main.xml
Executable file
@ -0,0 +1,62 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/hero_image"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:tint="?attr/colorOnSecondary"
|
||||
app:srcCompat="@drawable/logo" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/navigation_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:menu="@menu/drawer_view" />
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</layout>
|
||||
179
GDGFinderMaterial/app/src/main/res/layout/add_gdg_fragment.xml
Executable file
@ -0,0 +1,179 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.add.AddGdgViewModel" />
|
||||
</data>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".add.AddGdgFragment">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewIntro"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_normal"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:layout_marginEnd="@dimen/spacing_normal"
|
||||
android:text="@string/add_gdg"
|
||||
android:textColor="#AAAAAA"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="411dp"
|
||||
android:layout_height="230dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextName"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textViewIntro"
|
||||
app:srcCompat="@drawable/behind"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextName"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextEmail"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextEmail"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextCity"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextName"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextCity"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextCountry"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextEmail"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextCountry"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextRegion"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextCity"></EditText>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextRegion"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextWhy"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextCountry"></EditText>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/labelTextWhy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:text="@string/motivation"
|
||||
app:layout_constraintBottom_toTopOf="@+id/EditTextWhy"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextRegion" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/EditTextWhy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine"
|
||||
app:layout_constraintBottom_toTopOf="@+id/button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextRegion" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:backgroundTint="#00FF00"
|
||||
android:onClick="@{() -> viewModel.onSubmitApplication()}"
|
||||
android:text="Ok"
|
||||
android:textColor="#FF0000"
|
||||
android:textSize="8sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/EditTextWhy" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</layout>
|
||||
72
GDGFinderMaterial/app/src/main/res/layout/fragment_gdg_list.xml
Executable file
@ -0,0 +1,72 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.search.GdgListViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.example.android.gdg.MainActivity">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/chips"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"></HorizontalScrollView>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gdg_chapter_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chips"
|
||||
app:listData="@{viewModel.gdgList}"
|
||||
tools:listitem="@layout/list_item" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/waiting_for_location_and_network"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:showOnlyWhenEmpty="@{viewModel.gdgList}"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
134
GDGFinderMaterial/app/src/main/res/layout/home_fragment.xml
Executable file
@ -0,0 +1,134 @@
|
||||
<?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.
|
||||
-->
|
||||
<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="viewModel"
|
||||
type="com.example.android.gdgfinder.home.HomeViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="true"
|
||||
android:paddingBottom="@dimen/spacing_normal"
|
||||
tools:context=".home.HomeFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="4:3"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/behind" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:text="@string/about_google_developer_groups"
|
||||
android:textAppearance="?attr/textAppearanceHeadline5"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/image" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/intro_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/gdg_description_long"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13dp"
|
||||
android:text="@string/gdgs_are"
|
||||
android:textAppearance="?attr/textAppearanceHeadline6"
|
||||
app:layout_constraintEnd_toStartOf="@id/end_guideline"
|
||||
app:layout_constraintStart_toEndOf="@id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/intro_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/gdg_description_bullets"
|
||||
app:layout_constraintEnd_toStartOf="@+id/end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@+id/start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@id/subtitle" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/end_image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/spacing_normal"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="3:1.5"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/description"
|
||||
app:srcCompat="@drawable/wtm" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/start_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="@dimen/spacing_normal" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/end_guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_end="@dimen/spacing_normal" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/spacing_normal"
|
||||
android:onClick="@{() -> viewModel.onFabClicked()}"
|
||||
app:srcCompat="@drawable/ic_gdg" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
69
GDGFinderMaterial/app/src/main/res/layout/list_item.xml
Executable file
@ -0,0 +1,69 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
|
||||
<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"
|
||||
android:id="@+id/layout">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="chapter"
|
||||
type="com.example.android.gdgfinder.network.GdgChapter" />
|
||||
|
||||
<variable
|
||||
name="clickListener"
|
||||
type="com.example.android.gdgfinder.search.GdgClickListener" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> clickListener.onClick(chapter)}"
|
||||
tools:context="com.example.android.gdg.gdglist.GdgListFragment">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gdg_image"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="76dp"
|
||||
android:layout_marginLeft="@dimen/spacing_normal"
|
||||
android:layout_marginTop="26dp"
|
||||
android:layout_marginBottom="26dp"
|
||||
android:contentDescription="TODO"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_gdg" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/spacing_normal"
|
||||
android:layout_marginEnd="@dimen/spacing_normal"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@{chapter.name}"
|
||||
android:textAppearance="?textAppearanceHeadline6"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/gdg_image"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/gdg_image"
|
||||
app:layout_constraintTop_toTopOf="@+id/gdg_image"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="GDG Mountain View is really long so it will wrap in tools" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
22
GDGFinderMaterial/app/src/main/res/menu/drawer_view.xml
Executable file
@ -0,0 +1,22 @@
|
||||
<?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.
|
||||
-->
|
||||
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:title="@string/gdg_search" android:id="@+id/gdg_search"
|
||||
/>
|
||||
<item android:title="@string/gdg_apply" android:id="@+id/gdg_apply"/>
|
||||
</menu>
|
||||
5
GDGFinderMaterial/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Executable file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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>
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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
GDGFinderMaterial/app/src/main/res/mipmap-hdpi/ic_launcher.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
GDGFinderMaterial/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |