Merge branch 'dev' into localprofile

This commit is contained in:
Milos Kozak 2019-11-30 22:43:15 +01:00
commit 0fdc789e1f
11 changed files with 187 additions and 62 deletions

View file

@ -4,6 +4,7 @@ import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -12,6 +13,7 @@ import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.LinearSmoothScroller
@ -21,6 +23,8 @@ import info.nightscout.androidaps.R
import info.nightscout.androidaps.logging.L import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.bus.RxBus import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog
import info.nightscout.androidaps.plugins.constraints.objectives.dialogs.NtpProgressDialog
import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtpStatus
import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui
import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.ExamTask import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.ExamTask
import info.nightscout.androidaps.receivers.NetworkChangeReceiver import info.nightscout.androidaps.receivers.NetworkChangeReceiver
@ -101,18 +105,20 @@ class ObjectivesFragment : Fragment() {
} }
private fun scrollToCurrentObjective() { private fun scrollToCurrentObjective() {
for (i in 0 until ObjectivesPlugin.objectives.size) { activity?.runOnUiThread {
val objective = ObjectivesPlugin.objectives[i] for (i in 0 until ObjectivesPlugin.objectives.size) {
if (!objective.isStarted || !objective.isAccomplished) { val objective = ObjectivesPlugin.objectives[i]
context?.let { if (!objective.isStarted || !objective.isAccomplished) {
val smoothScroller = object : LinearSmoothScroller(it) { context?.let {
override fun getVerticalSnapPreference(): Int = SNAP_TO_START val smoothScroller = object : LinearSmoothScroller(it) {
override fun calculateTimeForScrolling(dx: Int): Int = super.calculateTimeForScrolling(dx) * 4 override fun getVerticalSnapPreference(): Int = SNAP_TO_START
override fun calculateTimeForScrolling(dx: Int): Int = super.calculateTimeForScrolling(dx) * 4
}
smoothScroller.targetPosition = i
objectives_recyclerview.layoutManager?.startSmoothScroll(smoothScroller)
} }
smoothScroller.targetPosition = i break
objectives_recyclerview.layoutManager?.startSmoothScroll(smoothScroller)
} }
break
} }
} }
} }
@ -126,7 +132,6 @@ class ObjectivesFragment : Fragment() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val objective = ObjectivesPlugin.objectives[position] val objective = ObjectivesPlugin.objectives[position]
holder.title.text = MainApp.gs(R.string.nth_objective, position + 1) holder.title.text = MainApp.gs(R.string.nth_objective, position + 1)
holder.revert.visibility = View.GONE
if (objective.objective != 0) { if (objective.objective != 0) {
holder.objective.visibility = View.VISIBLE holder.objective.visibility = View.VISIBLE
holder.objective.text = MainApp.gs(objective.objective) holder.objective.text = MainApp.gs(objective.objective)
@ -142,6 +147,8 @@ class ObjectivesFragment : Fragment() {
holder.verify.visibility = View.GONE holder.verify.visibility = View.GONE
holder.progress.visibility = View.GONE holder.progress.visibility = View.GONE
holder.accomplished.visibility = View.GONE holder.accomplished.visibility = View.GONE
holder.unFinish.visibility = View.GONE
holder.unStart.visibility = View.GONE
if (position == 0 || ObjectivesPlugin.objectives[position - 1].isAccomplished) if (position == 0 || ObjectivesPlugin.objectives[position - 1].isAccomplished)
holder.start.visibility = View.VISIBLE holder.start.visibility = View.VISIBLE
else else
@ -152,15 +159,16 @@ class ObjectivesFragment : Fragment() {
holder.progress.visibility = View.GONE holder.progress.visibility = View.GONE
holder.start.visibility = View.GONE holder.start.visibility = View.GONE
holder.accomplished.visibility = View.VISIBLE holder.accomplished.visibility = View.VISIBLE
holder.unFinish.visibility = View.VISIBLE
holder.unStart.visibility = View.GONE
} else if (objective.isStarted) { } else if (objective.isStarted) {
holder.gate.setTextColor(-0x1) holder.gate.setTextColor(-0x1)
holder.verify.visibility = View.VISIBLE holder.verify.visibility = View.VISIBLE
holder.verify.isEnabled = objective.isCompleted || objectives_fake.isChecked holder.verify.isEnabled = objective.isCompleted || objectives_fake.isChecked
holder.start.visibility = View.GONE holder.start.visibility = View.GONE
holder.accomplished.visibility = View.GONE holder.accomplished.visibility = View.GONE
if (objective.isRevertable) { holder.unFinish.visibility = View.GONE
holder.revert.visibility = View.VISIBLE holder.unStart.visibility = View.VISIBLE
}
holder.progress.visibility = View.VISIBLE holder.progress.visibility = View.VISIBLE
holder.progress.removeAllViews() holder.progress.removeAllViews()
for (task in objective.tasks) { for (task in objective.tasks) {
@ -203,39 +211,43 @@ class ObjectivesFragment : Fragment() {
holder.accomplished.text = MainApp.gs(R.string.accomplished, DateUtil.dateAndTimeString(objective.accomplishedOn)) holder.accomplished.text = MainApp.gs(R.string.accomplished, DateUtil.dateAndTimeString(objective.accomplishedOn))
holder.accomplished.setTextColor(-0x3e3e3f) holder.accomplished.setTextColor(-0x3e3e3f)
holder.verify.setOnClickListener { holder.verify.setOnClickListener {
holder.verify.visibility = View.INVISIBLE
NetworkChangeReceiver.grabNetworkStatus(context) NetworkChangeReceiver.grabNetworkStatus(context)
if (objectives_fake.isChecked) { if (objectives_fake.isChecked) {
objective.accomplishedOn = DateUtil.now() objective.accomplishedOn = DateUtil.now()
scrollToCurrentObjective() scrollToCurrentObjective()
startUpdateTimer() startUpdateTimer()
RxBus.send(EventObjectivesUpdateGui()) RxBus.send(EventObjectivesUpdateGui())
} else } else {
SntpClient.ntpTime(object : SntpClient.Callback() { // move out of UI thread
override fun run() { Thread {
activity?.runOnUiThread { NtpProgressDialog().show((context as AppCompatActivity).supportFragmentManager, "NtpCheck")
holder.verify.visibility = View.VISIBLE RxBus.send(EventNtpStatus(MainApp.gs(R.string.timedetection), 0))
SntpClient.ntpTime(object : SntpClient.Callback() {
override fun run() {
log.debug("NTP time: $time System time: ${DateUtil.now()}") log.debug("NTP time: $time System time: ${DateUtil.now()}")
SystemClock.sleep(300)
if (!networkConnected) { if (!networkConnected) {
ToastUtils.showToastInUiThread(context, R.string.notconnected) RxBus.send(EventNtpStatus(MainApp.gs(R.string.notconnected), 99))
} else if (success) { } else if (success) {
if (objective.isCompleted(time)) { if (objective.isCompleted(time)) {
objective.accomplishedOn = time objective.accomplishedOn = time
scrollToCurrentObjective() RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100))
startUpdateTimer() SystemClock.sleep(1000)
RxBus.send(EventObjectivesUpdateGui()) RxBus.send(EventObjectivesUpdateGui())
SystemClock.sleep(100)
scrollToCurrentObjective()
} else { } else {
ToastUtils.showToastInUiThread(context, R.string.requirementnotmet) RxBus.send(EventNtpStatus(MainApp.gs(R.string.requirementnotmet), 99))
} }
} else { } else {
ToastUtils.showToastInUiThread(context, R.string.failedretrievetime) RxBus.send(EventNtpStatus(MainApp.gs(R.string.failedretrievetime), 99))
} }
} }
} }, NetworkChangeReceiver.isConnected())
}, NetworkChangeReceiver.isConnected()) }.start()
}
} }
holder.start.setOnClickListener { holder.start.setOnClickListener {
holder.start.visibility = View.INVISIBLE
NetworkChangeReceiver.grabNetworkStatus(context) NetworkChangeReceiver.grabNetworkStatus(context)
if (objectives_fake.isChecked) { if (objectives_fake.isChecked) {
objective.startedOn = DateUtil.now() objective.startedOn = DateUtil.now()
@ -243,36 +255,43 @@ class ObjectivesFragment : Fragment() {
startUpdateTimer() startUpdateTimer()
RxBus.send(EventObjectivesUpdateGui()) RxBus.send(EventObjectivesUpdateGui())
} else } else
SntpClient.ntpTime(object : SntpClient.Callback() { // move out of UI thread
override fun run() { Thread {
activity?.runOnUiThread { NtpProgressDialog().show((context as AppCompatActivity).supportFragmentManager, "NtpCheck")
holder.start.visibility = View.VISIBLE RxBus.send(EventNtpStatus(MainApp.gs(R.string.timedetection), 0))
SntpClient.ntpTime(object : SntpClient.Callback() {
override fun run() {
log.debug("NTP time: $time System time: ${DateUtil.now()}") log.debug("NTP time: $time System time: ${DateUtil.now()}")
SystemClock.sleep(300)
if (!networkConnected) { if (!networkConnected) {
ToastUtils.showToastInUiThread(context, R.string.notconnected) RxBus.send(EventNtpStatus(MainApp.gs(R.string.notconnected), 99))
} else if (success) { } else if (success) {
objective.startedOn = time objective.startedOn = time
scrollToCurrentObjective() RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100))
startUpdateTimer() SystemClock.sleep(1000)
RxBus.send(EventObjectivesUpdateGui()) RxBus.send(EventObjectivesUpdateGui())
SystemClock.sleep(100)
scrollToCurrentObjective()
} else { } else {
ToastUtils.showToastInUiThread(context, R.string.failedretrievetime) RxBus.send(EventNtpStatus(MainApp.gs(R.string.failedretrievetime), 99))
} }
} }
} }, NetworkChangeReceiver.isConnected())
}, NetworkChangeReceiver.isConnected()) }.start()
} }
holder.revert.setOnClickListener { holder.unStart.setOnClickListener {
objective.accomplishedOn = 0 OKDialog.showConfirmation(activity, MainApp.gs(R.string.doyouwantresetstart)) {
objective.startedOn = 0 objective.startedOn = 0
if (position > 0) { scrollToCurrentObjective()
val prevObj = ObjectivesPlugin.objectives[position - 1] RxBus.send(EventObjectivesUpdateGui())
prevObj.accomplishedOn = 0
} }
}
holder.unFinish.setOnClickListener {
objective.accomplishedOn = 0
scrollToCurrentObjective() scrollToCurrentObjective()
RxBus.send(EventObjectivesUpdateGui()) RxBus.send(EventObjectivesUpdateGui())
} }
if (objective.hasSpecialInput && !objective.isAccomplished && objective.isStarted) { if (objective.hasSpecialInput && !objective.isAccomplished && objective.isStarted && objective.specialActionEnabled()) {
// generate random request code if none exists // generate random request code if none exists
val request = SP.getString(R.string.key_objectives_request_code, String.format("%1$05d", (Math.random() * 99999).toInt())) val request = SP.getString(R.string.key_objectives_request_code, String.format("%1$05d", (Math.random() * 99999).toInt()))
SP.putString(R.string.key_objectives_request_code, request) SP.putString(R.string.key_objectives_request_code, request)
@ -307,7 +326,8 @@ class ObjectivesFragment : Fragment() {
val progress: LinearLayout = itemView.findViewById(R.id.objective_progress) val progress: LinearLayout = itemView.findViewById(R.id.objective_progress)
val verify: Button = itemView.findViewById(R.id.objective_verify) val verify: Button = itemView.findViewById(R.id.objective_verify)
val start: Button = itemView.findViewById(R.id.objective_start) val start: Button = itemView.findViewById(R.id.objective_start)
val revert: Button = itemView.findViewById(R.id.objective_back) val unFinish: Button = itemView.findViewById(R.id.objective_unfinish)
val unStart: Button = itemView.findViewById(R.id.objective_unstart)
val inputHint: TextView = itemView.findViewById(R.id.objective_inputhint) val inputHint: TextView = itemView.findViewById(R.id.objective_inputhint)
val input: EditText = itemView.findViewById(R.id.objective_input) val input: EditText = itemView.findViewById(R.id.objective_input)
val enterButton: Button = itemView.findViewById(R.id.objective_enterbutton) val enterButton: Button = itemView.findViewById(R.id.objective_enterbutton)

View file

@ -104,7 +104,7 @@ object ObjectivesPlugin : PluginBase(PluginDescription()
fun completeObjectives(activity: Activity, request: String) { fun completeObjectives(activity: Activity, request: String) {
val requestCode = SP.getString(R.string.key_objectives_request_code, "") val requestCode = SP.getString(R.string.key_objectives_request_code, "")
var url = SP.getString(R.string.key_nsclientinternal_url, "").toLowerCase() var url = SP.getString(R.string.key_nsclientinternal_url, "").toLowerCase()
if (!url.endsWith("\"")) url = "$url/" if (!url.endsWith("/")) url = "$url/"
@Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() @Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString()
if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) { if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) {
SP.putLong("Objectives_" + "openloop" + "_started", DateUtil.now()) SP.putLong("Objectives_" + "openloop" + "_started", DateUtil.now())

View file

@ -0,0 +1,84 @@
package info.nightscout.androidaps.plugins.constraints.objectives.dialogs
import android.os.Bundle
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.bus.RxBus.toObservable
import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtpStatus
import info.nightscout.androidaps.utils.FabricPrivacy
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.overview_bolusprogress_dialog.*
import org.slf4j.LoggerFactory
class NtpProgressDialog : DialogFragment() {
private val log = LoggerFactory.getLogger(L.UI)
private val disposable = CompositeDisposable()
private val DEFAULT_STATE = MainApp.gs(R.string.timedetection)
private var state: String = DEFAULT_STATE
private var percent = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
dialog?.setTitle(String.format(MainApp.gs(R.string.objectives)))
isCancelable = false
state = savedInstanceState?.getString("state", DEFAULT_STATE) ?: DEFAULT_STATE
percent = savedInstanceState?.getInt("percent", 0) ?: 0
return inflater.inflate(R.layout.overview_bolusprogress_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
overview_bolusprogress_stop.setOnClickListener { dismiss() }
overview_bolusprogress_status.setText(state)
overview_bolusprogress_progressbar.setMax(100)
overview_bolusprogress_progressbar.setProgress(percent)
overview_bolusprogress_stop.text = MainApp.gs(R.string.close)
}
override fun onResume() {
super.onResume()
if (L.isEnabled(L.UI)) log.debug("onResume")
if (percent == 100) {
dismiss()
return
} else
dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
disposable.add(toObservable(EventNtpStatus::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ event: EventNtpStatus ->
if (L.isEnabled(L.UI)) log.debug("Status: " + event.status + " Percent: " + event.percent)
overview_bolusprogress_status?.text = event.status
overview_bolusprogress_progressbar?.progress = event.percent
if (event.percent == 100) {
SystemClock.sleep(100)
dismiss()
}
state = event.status
percent = event.percent
}) { FabricPrivacy.logException(it) }
)
}
override fun onPause() {
if (L.isEnabled(L.UI)) log.debug("onPause")
super.onPause()
disposable.clear()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("state", state)
outState.putInt("percent", percent)
super.onSaveInstanceState(outState)
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.constraints.objectives.events
import info.nightscout.androidaps.events.Event
class EventNtpStatus(val status: String, val percent: Int) : Event()

View file

@ -61,10 +61,6 @@ public abstract class Objective {
return true; return true;
} }
public boolean isRevertable() {
return false;
}
public boolean isAccomplished() { public boolean isAccomplished() {
return accomplishedOn != 0 && accomplishedOn < DateUtil.now(); return accomplishedOn != 0 && accomplishedOn < DateUtil.now();
} }
@ -107,6 +103,8 @@ public abstract class Objective {
return tasks; return tasks;
} }
public boolean specialActionEnabled() { return true; }
public void specialAction(Activity activity, String input) {} public void specialAction(Activity activity, String input) {}
public abstract class Task { public abstract class Task {

View file

@ -7,6 +7,7 @@ import java.util.List;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.T; import info.nightscout.androidaps.utils.T;
@ -38,6 +39,11 @@ public class Objective3 extends Objective {
}); });
} }
@Override
public boolean specialActionEnabled() {
return NSClientPlugin.getPlugin().nsClientService.isConnected && NSClientPlugin.getPlugin().nsClientService.hasWriteAuth;
}
@Override @Override
public void specialAction(Activity activity, String input) { public void specialAction(Activity activity, String input) {
ObjectivesPlugin.INSTANCE.completeObjectives(activity, input); ObjectivesPlugin.INSTANCE.completeObjectives(activity, input);

View file

@ -25,9 +25,4 @@ public class Objective5 extends Objective {
} }
}); });
} }
@Override
public boolean isRevertable() {
return true;
}
} }

View file

@ -16,12 +16,16 @@ package info.nightscout.androidaps.utils;
*/ */
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import info.nightscout.androidaps.logging.L;
/** /**
* {@hide} * {@hide}
* <p> * <p>
@ -35,7 +39,7 @@ import java.net.InetAddress;
* </pre> * </pre>
*/ */
public class SntpClient { public class SntpClient {
private static final String TAG = "SntpClient"; private static Logger log = LoggerFactory.getLogger(L.CORE);
//private static final int REFERENCE_TIME_OFFSET = 16; //private static final int REFERENCE_TIME_OFFSET = 16;
private static final int ORIGINATE_TIME_OFFSET = 24; private static final int ORIGINATE_TIME_OFFSET = 24;
@ -76,8 +80,10 @@ public class SntpClient {
} }
static void doNtpTime(final Callback callback) { static void doNtpTime(final Callback callback) {
log.debug("Time detection started");
callback.success = requestTime("time.google.com", 5000); callback.success = requestTime("time.google.com", 5000);
callback.time = getNtpTime() + SystemClock.elapsedRealtime() - getNtpTimeReference(); callback.time = getNtpTime() + SystemClock.elapsedRealtime() - getNtpTimeReference();
log.debug("Time detection ended: " + callback.success + " " + DateUtil.dateAndTimeString(getNtpTime()));
callback.run(); callback.run();
} }
@ -138,7 +144,7 @@ public class SntpClient {
mNtpTimeReference = responseTicks; mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime; mRoundTripTime = roundTripTime;
} catch (Exception e) { } catch (Exception e) {
Log.d(TAG, "request time failed: " + e); log.debug("request time failed: " + e);
return false; return false;
} }

View file

@ -68,11 +68,18 @@
android:text="@string/objectives_button_start" /> android:text="@string/objectives_button_start" />
<Button <Button
android:id="@+id/objective_back" android:id="@+id/objective_unfinish"
style="@style/Widget.AppCompat.Button.Borderless.Colored" style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/objectives_button_back" /> android:text="@string/objectives_button_unfinish" />
<Button
android:id="@+id/objective_unstart"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/objectives_button_unstart" />
<TextView <TextView
android:id="@+id/objective_inputhint" android:id="@+id/objective_inputhint"

View file

@ -1637,6 +1637,10 @@
<string name="format_carbs">%1$dg</string> <string name="format_carbs">%1$dg</string>
<string name="common_on">On</string> <string name="common_on">On</string>
<string name="common_off">Off</string> <string name="common_off">Off</string>
<string name="objectives_button_unfinish">Clear finished</string>
<string name="objectives_button_unstart">Clear started</string>
<string name="timedetection">Time detection</string>
<string name="doyouwantresetstart">Do you want reset objective start? You may lose your progress.</string>
<string name="nopumpselected">No pump selected</string> <string name="nopumpselected">No pump selected</string>
<string name="setupwizard_units_prompt">Select units you want to display values in</string> <string name="setupwizard_units_prompt">Select units you want to display values in</string>
<string name="key_ns_uploadlocalprofile" translatable="false">ns_uploadlocalprofile</string> <string name="key_ns_uploadlocalprofile" translatable="false">ns_uploadlocalprofile</string>

View file

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.3.61'
repositories { repositories {
google() google()
jcenter() jcenter()