From 941bb3e1afbccc79ae7ad5443cf43a01344cb614 Mon Sep 17 00:00:00 2001 From: jens Date: Wed, 5 Jan 2022 18:06:26 +0100 Subject: [PATCH] profile trigger --- .../java/com/jens/automation2/Rule.java | 27 ++- .../automation2/ActivityMainProfiles.java | 12 +- .../jens/automation2/ActivityManageRule.java | 16 +- ...ctivityManageTriggerDeviceOrientation.java | 14 +- .../com/jens/automation2/Miscellaneous.java | 10 +- .../java/com/jens/automation2/Profile.java | 160 ++++++++++++++++-- .../java/com/jens/automation2/Trigger.java | 73 ++++---- app/src/main/res/drawable/arrow_azimuth.png | Bin 0 -> 1054 bytes app/src/main/res/drawable/arrow_pitch.png | Bin 0 -> 2465 bytes app/src/main/res/drawable/arrow_roll.png | Bin 0 -> 2268 bytes ...vity_manage_trigger_device_orientation.xml | 27 ++- .../activity_manage_trigger_profile.xml | 10 ++ app/src/main/res/values-de/strings.xml | 11 +- app/src/main/res/values-es/strings.xml | 9 + app/src/main/res/values-it/strings.xml | 9 + app/src/main/res/values-nl/strings.xml | 9 + app/src/main/res/values/strings.xml | 3 + .../metadata/android/en-US/changelogs/116.txt | 5 +- 18 files changed, 313 insertions(+), 82 deletions(-) create mode 100644 app/src/main/res/drawable/arrow_azimuth.png create mode 100644 app/src/main/res/drawable/arrow_pitch.png create mode 100644 app/src/main/res/drawable/arrow_roll.png diff --git a/app/src/apkFlavor/java/com/jens/automation2/Rule.java b/app/src/apkFlavor/java/com/jens/automation2/Rule.java index ec070257..d5d9c288 100644 --- a/app/src/apkFlavor/java/com/jens/automation2/Rule.java +++ b/app/src/apkFlavor/java/com/jens/automation2/Rule.java @@ -12,7 +12,6 @@ import android.widget.Toast; import com.google.android.gms.location.DetectedActivity; import com.jens.automation2.receivers.ActivityDetectionReceiver; -import java.sql.Time; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -834,8 +833,32 @@ public class Rule implements Comparable return ruleCandidates; }*/ + + public static ArrayList findRuleCandidatesByTriggerProfile(Profile profile) + { + ArrayList ruleCandidates = new ArrayList(); + + for(Rule oneRule : ruleCollection) + { + innerloop: + for(Trigger oneTrigger : oneRule.getTriggerSet()) + { + if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.profileActive) + { + String profileName = oneTrigger.getTriggerParameter2().split(triggerParameter2Split)[0]; + if(profileName.equals(profile.getName())) + { + ruleCandidates.add(oneRule); + break innerloop; //if the profile is found we don't need to search the other triggers in the same rule + } + } + } + } + + return ruleCandidates; + } - public static ArrayList findRuleCandidatesByProfile(Profile profile) + public static ArrayList findRuleCandidatesByActionProfile(Profile profile) { ArrayList ruleCandidates = new ArrayList(); diff --git a/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java b/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java index 02fe32eb..d83ffa8f 100644 --- a/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java +++ b/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java @@ -184,8 +184,16 @@ public class ActivityMainProfiles extends ActivityGeneric startActivityForResult(manageSpecificProfileIntent, 2000); break; case 2: - if(profile.delete(myAutomationService)) - updateListView(); + Rule user = profile.isInUseByRules(); + if(user == null) + { + if (profile.delete(ActivityMainProfiles.this)) + updateListView(); + else + Toast.makeText(ActivityMainProfiles.this, getResources().getString(R.string.profileCouldNotBeDeleted), Toast.LENGTH_LONG).show(); + } + else + Toast.makeText(ActivityMainProfiles.this, String.format(getResources().getString(R.string.ruleXIsUsingProfileY), user.getName(), profile.getName()), Toast.LENGTH_LONG).show(); break; } } diff --git a/app/src/main/java/com/jens/automation2/ActivityManageRule.java b/app/src/main/java/com/jens/automation2/ActivityManageRule.java index def6c39b..83fc39aa 100644 --- a/app/src/main/java/com/jens/automation2/ActivityManageRule.java +++ b/app/src/main/java/com/jens/automation2/ActivityManageRule.java @@ -609,10 +609,18 @@ public class ActivityManageRule extends Activity } else if(triggerType == Trigger_Enum.profileActive) { - newTrigger.setTriggerType(Trigger_Enum.profileActive); - Intent profileTriggerEditor = new Intent(myContext, ActivityManageTriggerProfile.class); - startActivityForResult(profileTriggerEditor, requestCodeTriggerProfileAdd); - return; + if(Profile.getProfileCollection().size() > 0) + { + newTrigger.setTriggerType(Trigger_Enum.profileActive); + Intent profileTriggerEditor = new Intent(myContext, ActivityManageTriggerProfile.class); + startActivityForResult(profileTriggerEditor, requestCodeTriggerProfileAdd); + return; + } + else + { + Toast.makeText(context, getResources().getString(R.string.noProfilesCreateOneFirst), Toast.LENGTH_LONG).show(); + return; + } } else if(triggerType == Trigger_Enum.activityDetection) { diff --git a/app/src/main/java/com/jens/automation2/ActivityManageTriggerDeviceOrientation.java b/app/src/main/java/com/jens/automation2/ActivityManageTriggerDeviceOrientation.java index 0f1922fb..ef3fb199 100644 --- a/app/src/main/java/com/jens/automation2/ActivityManageTriggerDeviceOrientation.java +++ b/app/src/main/java/com/jens/automation2/ActivityManageTriggerDeviceOrientation.java @@ -43,7 +43,7 @@ public class ActivityManageTriggerDeviceOrientation extends Activity { desiredAzimuth = Float.parseFloat(etDesiredAzimuth.getText().toString()); desiredAzimuthTolerance = Float.parseFloat(etDesiredAzimuthTolerance.getText().toString()); - if (Math.abs(azimuth) <= Math.abs(desiredAzimuth - desiredAzimuthTolerance) || Math.abs(azimuth) <= desiredAzimuth + desiredAzimuthTolerance) + if (desiredAzimuthTolerance == 180 || (desiredAzimuth - desiredAzimuthTolerance <= azimuth && azimuth <= desiredAzimuth + desiredAzimuthTolerance)) { tvAppliesAzimuth.setText(getResources().getString(R.string.yes)); tvAppliesAzimuth.setTextColor(Color.GREEN); @@ -63,7 +63,7 @@ public class ActivityManageTriggerDeviceOrientation extends Activity { desiredPitch = Float.parseFloat(etDesiredPitch.getText().toString()); desiredPitchTolerance = Float.parseFloat(etDesiredPitchTolerance.getText().toString()); - if (Math.abs(pitch) <= Math.abs(desiredPitch - desiredPitchTolerance) || Math.abs(pitch) <= desiredPitch + desiredPitchTolerance) + if (desiredPitchTolerance == 180 || (desiredPitch - desiredPitchTolerance <= pitch && pitch <= desiredPitch + desiredPitchTolerance)) { tvAppliesPitch.setText(getResources().getString(R.string.yes)); tvAppliesPitch.setTextColor(Color.GREEN); @@ -83,7 +83,7 @@ public class ActivityManageTriggerDeviceOrientation extends Activity { desiredRoll = Float.parseFloat(etDesiredRoll.getText().toString()); desiredRollTolerance = Float.parseFloat(etDesiredRollTolerance.getText().toString()); - if (Math.abs(roll) <= Math.abs(desiredRoll - desiredRollTolerance) || Math.abs(roll) <= desiredRoll + desiredRollTolerance) + if (desiredRollTolerance == 180 || (desiredRoll - desiredRollTolerance <= roll && roll <= desiredRoll + desiredRollTolerance)) { tvAppliesRoll.setText(getResources().getString(R.string.yes)); tvAppliesRoll.setTextColor(Color.GREEN); @@ -159,14 +159,16 @@ public class ActivityManageTriggerDeviceOrientation extends Activity @Override public void onClick(View v) { + // Round the values. Too long decimals will destroy the layout + if(!StringUtils.isEmpty(currentAzimuth.getText())) - etDesiredAzimuth.setText(currentAzimuth.getText()); + etDesiredAzimuth.setText(String.valueOf(Math.round(Float.parseFloat(currentAzimuth.getText().toString())))); if(!StringUtils.isEmpty(currentPitch.getText())) - etDesiredPitch.setText(currentPitch.getText()); + etDesiredPitch.setText(String.valueOf(Math.round(Float.parseFloat(currentPitch.getText().toString())))); if(!StringUtils.isEmpty(currentRoll.getText())) - etDesiredRoll.setText(currentRoll.getText()); + etDesiredRoll.setText(String.valueOf(Math.round(Float.parseFloat(currentRoll.getText().toString())))); } }); diff --git a/app/src/main/java/com/jens/automation2/Miscellaneous.java b/app/src/main/java/com/jens/automation2/Miscellaneous.java index 5f8c28b6..25da177f 100644 --- a/app/src/main/java/com/jens/automation2/Miscellaneous.java +++ b/app/src/main/java/com/jens/automation2/Miscellaneous.java @@ -216,7 +216,15 @@ public class Miscellaneous extends Service return null; } - @Override + public static int boolToInt(boolean input) + { + if(input) + return 1; + else + return 0; + } + + @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub diff --git a/app/src/main/java/com/jens/automation2/Profile.java b/app/src/main/java/com/jens/automation2/Profile.java index 99ab5f1d..83624037 100644 --- a/app/src/main/java/com/jens/automation2/Profile.java +++ b/app/src/main/java/com/jens/automation2/Profile.java @@ -1,5 +1,6 @@ package com.jens.automation2; +import android.app.NotificationManager; import android.content.ContentValues; import android.content.Context; import android.media.AudioManager; @@ -290,18 +291,12 @@ public class Profile implements Comparable return null; } - - public boolean delete(AutomationService myAutomationService) - { - // TODO Auto-generated method stub - return false; - } private boolean applyRingTone(File ringtoneFile, int ringtoneType, Context context) { Miscellaneous.logEvent("i", "Profile", "Request to set ringtone to " + ringtoneFile.getAbsolutePath(), 3); - if(!ringtoneFile.exists() | !ringtoneFile.canRead()) + if(!ringtoneFile.exists() || !ringtoneFile.canRead()) { String message = "Ringtone file does not exist or cannot read it: " + ringtoneFile.getAbsolutePath(); Miscellaneous.logEvent("i", "Profile", message, 3); @@ -392,7 +387,7 @@ public class Profile implements Comparable } // Check if rules reference this profile - ArrayList rulesThatReferenceMe = Rule.findRuleCandidatesByProfile(this); + ArrayList rulesThatReferenceMe = Rule.findRuleCandidatesByActionProfile(this); if(rulesThatReferenceMe.size() > 0) { for(Rule oneRule : rulesThatReferenceMe) @@ -402,7 +397,7 @@ public class Profile implements Comparable if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.profileActive) { String[] parts = oneTrigger.getTriggerParameter2().split(Trigger.triggerParameter2Split); - parts[1] = this.name; + parts[0] = this.name; oneTrigger.setTriggerParameter2(Miscellaneous.explode(Trigger.triggerParameter2Split, parts)); // We don't need to save the file. This will happen anyway in PointOfInterest.writePoisToFile() below. @@ -437,19 +432,50 @@ public class Profile implements Comparable return false; } - public boolean delete() - { - for(int i = 0; i< Profile.getProfileCollection().size(); i++) + public Rule isInUseByRules() + { + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.profileActive)) { - if(Profile.getProfileCollection().get(i).getName().equals(this.getName())) + for (Rule rule : Rule.findRuleCandidatesByTriggerProfile(this)) { - Profile.getProfileCollection().remove(0); - - // write to file - return XmlFileInterface.writeFile(); + return rule; } } - + else if(Rule.isAnyRuleUsing(Action_Enum.changeSoundProfile)) + { + for (Rule rule : Rule.findRuleCandidatesByActionProfile(this)) + { + return rule; + } + } + + return null; + } + + public boolean delete(Context context) + { + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.profileActive)) + { + for (Rule rule : Rule.findRuleCandidatesByTriggerProfile(this)) + { + Toast.makeText(context, String.format(context.getResources().getString(R.string.ruleXIsUsingProfileY), rule.getName(), this.getName()), Toast.LENGTH_LONG).show(); + return false; + } + } + else if(Rule.isAnyRuleUsing(Action_Enum.changeSoundProfile)) + { + for (Rule rule : Rule.findRuleCandidatesByActionProfile(this)) + { + Toast.makeText(context, String.format(context.getResources().getString(R.string.ruleXIsUsingProfileY), rule.getName(), this.getName()), Toast.LENGTH_LONG).show(); + return false; + } + } + else + { + profileCollection.remove(this); + return XmlFileInterface.writeFile(); + } + return false; } @@ -557,6 +583,104 @@ public class Profile implements Comparable } } + public boolean areMySettingsCurrentlyActive(Context context) + { + Miscellaneous.logEvent("i", "Profile " + this.getName(), "Checking if profile's settings are currently active.", 3); + + try + { + AudioManager am = (AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE); + NotificationManager mNotificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); + + if(changeSoundMode) + { + if(am.getRingerMode() != soundMode) + return false; + } + + if(changeDndMode && Build.VERSION.SDK_INT >= 23) + { + if(mNotificationManager.getCurrentInterruptionFilter() != dndMode) + return false; + } + + if(changeVolumeMusicVideoGameMedia) + { + if(am.getStreamVolume(AudioManager.STREAM_MUSIC) != volumeMusic) + return false; + } + + if(changeVolumeNotifications) + { + if(am.getStreamVolume(AudioManager.STREAM_NOTIFICATION) != volumeNotifications) + return false; + } + + if(changeVolumeAlarms) + { + if(am.getStreamVolume(AudioManager.STREAM_ALARM) != volumeAlarms) + return false; + } + +// if(changeIncomingCallsRingtone) +// { +// if (incomingCallsRingtone != null) +// { +// applyRingTone(incomingCallsRingtone, RingtoneManager.TYPE_RINGTONE, context); +// } +// } + + if(changeVibrateWhenRinging) + { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + int currentSetting = android.provider.Settings.System.getInt(context.getContentResolver(), "vibrate_when_ringing"); + if(currentSetting != Miscellaneous.boolToInt(vibrateWhenRinging)) + return false; + } + else + { + int currentSetting = am.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); + if(currentSetting != Miscellaneous.boolToInt(vibrateWhenRinging)) + return false; + } + } + +// if(changeNotificationRingtone) +// if(notificationRingtone != null) +// applyRingTone(notificationRingtone, RingtoneManager.TYPE_NOTIFICATION, context); + + if(changeScreenLockUnlockSound) + { + int currentSetting = android.provider.Settings.System.getInt(context.getContentResolver(), "lockscreen_sounds_enabled"); + if(currentSetting != Miscellaneous.boolToInt(screenLockUnlockSound)) + return false; + } + + if(changeAudibleSelection) + { + int currentSetting = android.provider.Settings.System.getInt(context.getContentResolver(), android.provider.Settings.System.SOUND_EFFECTS_ENABLED); + if(currentSetting != Miscellaneous.boolToInt(audibleSelection)) + return false; + } + + if(changeHapticFeedback) + { + int currentSetting = android.provider.Settings.System.getInt(context.getContentResolver(), android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED); + if(currentSetting != Miscellaneous.boolToInt(hapticFeedback)) + return false; + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Profile " + this.getName(), "Error while checking if profile settings are currently active. " + Log.getStackTraceString(e), 1); + } + + Miscellaneous.logEvent("i", "Profile " + this.getName(), "This profile's settings are currently active.", 4); + + return true; + } + @Override public String toString() { diff --git a/app/src/main/java/com/jens/automation2/Trigger.java b/app/src/main/java/com/jens/automation2/Trigger.java index 32518313..9c0ee66b 100644 --- a/app/src/main/java/com/jens/automation2/Trigger.java +++ b/app/src/main/java/com/jens/automation2/Trigger.java @@ -279,32 +279,41 @@ public class Trigger boolean checkProfileActive() { - try + String demandedProfileName = getTriggerParameter2().split(Trigger.triggerParameter2Split)[0]; + boolean checkSettings = Boolean.parseBoolean(getTriggerParameter2().split(Trigger.triggerParameter2Split)[1]); + + if(checkSettings) { - String demandedProfileName = getTriggerParameter2().split(Trigger.triggerParameter2Split)[0]; - Profile lastProfile = null; - - if(Profile.profileActivationHistory.size() > 0) - { - lastProfile = Profile.profileActivationHistory.get(Profile.profileActivationHistory.size() - 1); - - if (getTriggerParameter()) - return demandedProfileName.equals(lastProfile.getName()); - else - return !demandedProfileName.equals(lastProfile.getName()); - } - else - return !getTriggerParameter(); + Profile profile = Profile.getByName(demandedProfileName); + return profile.areMySettingsCurrentlyActive(Miscellaneous.getAnyContext()); } - catch(Exception e) + else { - Miscellaneous.logEvent("w", "Trigger", "Error checking profile trigger.", 4); + try + { + Profile lastProfile = null; + + if (Profile.profileActivationHistory.size() > 0) + { + lastProfile = Profile.profileActivationHistory.get(Profile.profileActivationHistory.size() - 1); + + if (getTriggerParameter()) + return demandedProfileName.equals(lastProfile.getName()); + else + return !demandedProfileName.equals(lastProfile.getName()); + } + else + return !getTriggerParameter(); + } + catch (Exception e) + { + Miscellaneous.logEvent("w", "Trigger", "Error checking profile trigger.", 4); + } } return false; } - boolean checkDeviceOrientation() { String deviceOrientationPieces[] = getTriggerParameter2().split(Trigger.triggerParameter2Split); @@ -320,13 +329,7 @@ public class Trigger if(desiredAzimuthTolerance < 180) { - if ( - !( - Math.abs(currentAzimuth) <= Math.abs(desiredAzimuth - desiredAzimuthTolerance) - || - Math.abs(currentAzimuth) <= desiredAzimuth + desiredAzimuthTolerance - ) - ) + if (!(desiredAzimuth - desiredAzimuthTolerance <= currentAzimuth && currentAzimuth <= desiredAzimuth + desiredAzimuthTolerance)) { Miscellaneous.logEvent("i", "DeviceOrientation", "Azimuth outside of tolerance area.", 5); if (getTriggerParameter()) @@ -338,15 +341,7 @@ public class Trigger if(desiredPitchTolerance < 180) { - if ( - !( - ( - Math.abs(currentPitch) <= Math.abs(desiredPitch - desiredPitchTolerance) - || - Math.abs(currentPitch) <= desiredPitch + desiredPitchTolerance - ) - ) - ) + if (!(desiredPitch - desiredPitchTolerance <= currentPitch && currentPitch <= desiredPitch + desiredPitchTolerance)) { Miscellaneous.logEvent("i", "DeviceOrientation", "Pitch outside of tolerance area.", 5); if (getTriggerParameter()) @@ -358,15 +353,7 @@ public class Trigger if(desiredRollTolerance < 180) { - if ( - !( - ( - Math.abs(currentRoll) <= Math.abs(desiredRoll - desiredRollTolerance) - || - Math.abs(currentRoll) <= desiredRoll + desiredRollTolerance - ) - ) - ) + if (!(desiredRoll - desiredRollTolerance <= currentRoll && currentRoll <= desiredRoll + desiredRollTolerance)) { Miscellaneous.logEvent("i", "DeviceOrientation", "Roll outside of tolerance area.", 5); if (getTriggerParameter()) diff --git a/app/src/main/res/drawable/arrow_azimuth.png b/app/src/main/res/drawable/arrow_azimuth.png new file mode 100644 index 0000000000000000000000000000000000000000..371d6e25a083c6ba95256de0e98eecc5e0a9dc89 GIT binary patch literal 1054 zcmV+(1mXLMP)N2bPDNB8 zb~7$DE-^4L^m3s900WvyL_t(&L+zORQxZ`W$NkgxzsNKJAwV=N@zs<$cP3GJKhs35 z9G$5d)269kO7Oz+5PVKHl|BBK+qugw?JgH2g_R%fcjh~eEa>@g&V8KI!^OqL#l^*C zfV^+MA+&WaIsV~~Aivywa>Yw%U=gGH%Vq3dDicc}OYSW0SG|yt+&`~%-+UULXNWVJ zCljlO^mjBE$Rhbq8YgG0h@8Sl1ll#R8%3o%>9NVtFioq;*-KjLiOA9RxMN;jxvYjx5fRL=g?gi_Kt2^5gH;vI77T zgkl9?FenmFsxtt3(G0ObsGU)f1b2TlhjGAeq=ryHhTaUV0EUjN-}F z!}>3FYW-Azl7h293t|iB36zpy5Q-kXJb`UK-w?$8ti)b3)=(a?KwOCfL&~Xi+pOf#wH=-nHju zOoMa9wIe3Q3czsk;A(vlEO4P8)&&cMTImn$VUa+eO|RH=I)Wnae>MGQ^!e1sq#t|uCv~}K=tk*<` zWg`ii{!CLCI-hDC1gwK_#tMWsQHHeFPasR9&@-F;SPHTZMhBso1!D4HGLkG&2;|?n z=zQ?auP0Ty$0U$;HLR#l^+d9qQ@% Y3!nqY^?g?A^8f$<07*qoM6N<$f@(74g8%>k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/arrow_pitch.png b/app/src/main/res/drawable/arrow_pitch.png new file mode 100644 index 0000000000000000000000000000000000000000..916663270b56375a39c8687013c762efa5a22312 GIT binary patch literal 2465 zcmV;S310SzP)N2bPDNB8 zb~7$DE-^4L^m3s900|vQL_t(&L)BVoY*g13X0b{UO4JY{Eb#-{L~RLbP%8zM3KB5b zGjHb2oAHeCf-%M+VB=+G-prfT9`6nUI|~>Lp7AX2&F=Az@dkv{L{SBiDuEjJN1G@O ze^3*tL8z%j)Z25$H%JjNc2)7l{UrYQ+=uRW@44Uk&b_nZX`v znP&?9ODV(&BP58z93oml{8U-z)#|K)wRHIM#JjQCb%z^c2G$aqQ#(>b-+Gl8+OHEs z+xzhE8eyHoB)2Xw#~8f^gFUp4ezyE@;^e?$s=>75fI_duqLXgKpw)Osr*|Np7Q-4J3#YY7~M{C})oKz+eoVbqrJ=UfAlP7#*( zw^yAU_z4}7Ie_}SsI2qF)XpKoc_v7{Bl>BvE4+(_A48cZx)qddf^|V^L5k_@;ag>m z(cjW=<^aWQw?+v633o&77$OF{c&V}@^-CIl3}@muUN~4E=~P0I#BUf&_@Sb%*FeLW zLC?TPD_L`2p~e>fx2}1HFxEKPcj7_?)RUET$Yb!2-dxF9{D)Z~@+}GjR{oOuaPw*L z`g3%M{OAmSu)fq2YiERcNiA?Ezd7WImeBBmfF0PLYYa?rC=QU4U|sr4>mxsuZJZ=ZQ)o(M^lhi%1p$6+o8B&tu~6mloG47e86fuy8kQfB>pKk2=x4aH zI3dm&nyP4tFG{3Sy1t&{16?ZnU|NQC_6RZ9f;luSKh!2)4r>h)JRfQ>ggR?bw4WK@ zLc>qQ-pU>xT`_)}YZO7U{rc|Yhgmc%FLJ7Sa`Ma)(SaFcgnsh+@$)G}hq6mOagl+( z16LKAr(1CN_tNkaQS6d_qcQiT_Eq!ukCFYgzAxb1%;<&S20Bb09BD~p7dXQr zV^5`t>^FvHUq60sCk;OlK;&mCqc@sc>j#@MovuMSH9i9A_Q{)V$&GZFJb?ZpTPp-% zN0Mo?xUbg#-w?^l7Xq*3)p|!2<}fY-dbGKU=J>9fQ-iDNFnIuz$>#VzaZhCr*;91^ z6>FBM^Dc=9qS!61f>he3sPj&0t^Tev4U6iLmZS(h zz5GNlpTV|^DhFNxO+!Ho0FkB1F9PJ7*EtjOUJGna&FNwBlyqHwA{AH=9a6foqiNLI zV}Ap$R7k^%BDdxuk46)Tpz%D`-f#8}|3UuyVE*mV=Zf9o4uvH~^ljG&2bNrK3oh9- zqUE3WX;+8>S^;bcXh>c(_fBWACZ9gnB+c7+Xp;6-^(=`oGtd}^aUN5&I3-V-F5ErqC5H2&=vk*3v`Kbyw}1M(K}<`!Y-B3 z@DBoP`bsFK`2Kh2 zDzDZzs51F7cRBYRK!LVEgB8Me5w3om=pE6|p)kHi!{5UpPXwP8igb?oCpgxg#0!h| zX76<7pA`^b0Lxih`~jX5#u9~^;H?HH{MZvTOgdX__V=J0z_B`e^d692J`X*m^^u*R zUN^8VuwWw9eCQ@;@qh1Wp{y~M#aRNm3@_frmo1HJ2nhQmAiS9l$vi4MQqQVQK95RB ze2poi7Gl_FHG>pCdgv^4iW_x;I5vOlGEG$0@LlKw9Btkt-Zvg=qS5N8=hKEFXv;aEhDg=gS8*r-s%+&)v$|BLBe2V)`(= z^mnDP?<&~+J9ra`JAouYwQhsjEzcheAWU5&o1tp=GM31FOfh}yH8|80X@$a{ap3a< z5WRo2HQ8C>jx67wa3El%H$h7I6~^ELd;rRaT|go#dp`j|cmO2)_Os*5FQtA6;DR?8 zJBOV`?&$9gj>yA8cl6=mro_~nZT(!`=^=TqME_Sb^-ixTZHPPrJM?_9TY4TyJmc)X f{FEMYR;>6Bck|^y@DAh*00000NkvXXu0mjf>2IOK literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/arrow_roll.png b/app/src/main/res/drawable/arrow_roll.png new file mode 100644 index 0000000000000000000000000000000000000000..98c6ac73a3f9cf8eb4fa9f8543bcfb8177d0cab1 GIT binary patch literal 2268 zcmV<22qX82P)N2bPDNB8 zb~7$DE-^4L^m3s900>w~L_t(&L+x5=R})zf^_L9|B&=Z%h%0t8tET98#` zbQBT@2@v)r$mTYqG@>AQ&itk~bzgTvcc&x7q~m;;I;TDmUXpj;tGacoIye3|0}HoU&%~IuIHFhJL{c`wp3CQLy8M#zb>>+0;D&@SDx{D|7Jy5?;9ffTfx^wtD!LZgljY;& zhgz9?fk1d!3FX1J0F;lNvZ8^vJlQQR3oM<&PayCX%7yHs(--?X%>80c8S2>ClIm0LmU7(?(Y*@x9E_=%o`4N>+zgyw$VBy{Ii9rqxb~<#f^r zALK;g{X=^1c?5tK%xAu){N5s)-~XC8n6^Lm#q>H4D-qur0zjhYb#4>&dRew(Mrs({ zzA{<+h%6AMIn6}XtwFk>V-1K)FsDS-epb>kpIFW~AMXlCY+KK61b`ZZedL6PjLkX6 zh=$`nwZ}`xg4N-X5PAi@`x+2o$4%n~?}%&P&t?U#IVsB-i~<11DI)%h+5^(u?nOdZ zPc=kDF^vTVuUi^DtbSxG{_jDxMo8I_m^u)64IltiS)Q<^)w~d7Fb90|de3q$LFUB& zeRg7u{H_pvh!zC+9!Z$n;!8T$yGW-AwbQKR!QXuIY6n{aX!FF*^>4-bWLHGJC?EaI zH?MXiHr)h!v3Z&}lNcD)4+*lRV*CpSpmy9!Tv0&Em2X%N!ajULiRWSNX9uRrh(bE3 zT=y2=yxL2Lj`*!SU;lLVA$q9*WzcPaE%ZQ8CrIEWJ*4yoy@h%N38V&sum&9$wfV0; zNGd>LrelsOFAA0FHt0if8^eM3DG_xxr;l>si!kqN(%m8R#(RD)~CKLJk~G0?_oZQX3UyDV+}0 zMpa!80-=wtvME+gp}t#0u~UP97gFC{(h*205LnZAh)b7TDg`)PfI#I>ubYA@jvXDP z?gi1>x)=onZ?5bKrW^>24v-61P=8VX8!jLn<5wtbQn8%B0Y>c^4j-!>+4>dU>PZ2c1chUYfVrX0Q}IbTfUuFg=d=%oQahBlt=0GK@N!M=5(w%DAH3&!3nLm*oGGd1db`;j zG?euScRMK%Wm&X!JwK#tn@u$1Z_lsRSm2f10ik0 z8=L&@-3W-V7gMFjEfqME*GOH*EEEkGc&LGZhBF#xS?8aJ(LfwyAtA5D#|w=fMA*^n zpxQ!0ctc-I&zrHuBN4qkrYSNAS!tk0LexAnlJc08L$_cK1-`Brmx~Xa>m2$!yM2v| zI8Y!)G|F2|GmN0*;vAMrvKRKh=6S0u(^@_;0Fc+*+t3}#GF}0pc)|SxmPH?~EHl-3tKC42)z~TDeAvSiG)O!XTn+#m?1#c*Tp-e<64W< zrXGlWg;cTl7as#v4{dy3NTZp`=~(BRj{-Av6d9W)GW)$fuqNQ4DD;ya z1fMt4Yv|waQe9Er62Db;2L+*aN{QKMFo;{|9&F@Vl&G?qoi73=A;<}cQ@Opq1R(nb z73dWR#KdVI<9y@<)UXj45CR@UW5im~y~w~iZnP=6PQa?w23TL~mT}h5$&}8~*~1O_P;aE^3zm0000 + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + + android:layout_height="wrap_content" + android:gravity="center_vertical" > + + + + + + + +