0.9.1-alpha - Code Version 9 -Fix minor bugs with manual location #57

Merged
s416204 merged 11 commits from develop into master 2018-12-01 17:13:13 +01:00
11 changed files with 127 additions and 135 deletions
Showing only changes of commit f6cfb7f352 - Show all commits

View File

@ -10,7 +10,7 @@ android {
applicationId "com.uam.wmi.findmytutor" applicationId "com.uam.wmi.findmytutor"
minSdkVersion 22 minSdkVersion 22
targetSdkVersion 27 targetSdkVersion 27
versionCode 3 versionCode 7
versionName "0.9.0-alpha" versionName "0.9.0-alpha"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -62,5 +62,6 @@ dependencies {
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:4.0.0' implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:4.0.0'
// FloatingBarMenu // FloatingBarMenu
implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'org.apache.commons:commons-collections4:4.0'
} }

View File

@ -10,21 +10,17 @@ import android.os.Bundle;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Switch;
import com.auth0.android.jwt.Claim; import com.auth0.android.jwt.Claim;
import com.auth0.android.jwt.JWT; import com.auth0.android.jwt.JWT;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException; import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import com.uam.wmi.findmytutor.R; import com.uam.wmi.findmytutor.R;
import com.uam.wmi.findmytutor.model.JwtToken; import com.uam.wmi.findmytutor.model.JwtToken;
import com.uam.wmi.findmytutor.model.LdapUser;
import com.uam.wmi.findmytutor.model.User; import com.uam.wmi.findmytutor.model.User;
import com.uam.wmi.findmytutor.model.ValidateUser; import com.uam.wmi.findmytutor.model.ValidateUser;
import com.uam.wmi.findmytutor.network.ApiClient; import com.uam.wmi.findmytutor.network.ApiClient;
@ -44,7 +40,6 @@ import okhttp3.ResponseBody;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
// UI references.
private AutoCompleteTextView mLoginNameView; private AutoCompleteTextView mLoginNameView;
private EditText mPasswordView; private EditText mPasswordView;
private View mProgressView; private View mProgressView;
@ -153,7 +148,8 @@ public class LoginActivity extends AppCompatActivity {
private void loginProcess(String email, String password) { private void loginProcess(String email, String password) {
ValidateUser user = new ValidateUser(email, password); ValidateUser user = new ValidateUser(email, password);
disposable.add(ldapService.validate(user) disposable.add(ldapService.validate(user)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -163,9 +159,9 @@ public class LoginActivity extends AppCompatActivity {
private void getUserProfile(String userId) { private void getUserProfile(String userId) {
disposable.add(userService.getUserById(userId) disposable.add(userService.getUserById(userId)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(this::saveUserProfileToSharedPreferences, this::handleError)); .subscribe(this::saveUserProfileToSharedPreferences, this::handleError));
} }
private void showSnackBarMessage(String message) { private void showSnackBarMessage(String message) {
@ -181,9 +177,9 @@ public class LoginActivity extends AppCompatActivity {
Claim userId = jwt.getClaim("nameid"); Claim userId = jwt.getClaim("nameid");
Claim role = jwt.getClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); Claim role = jwt.getClaim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
if(Objects.requireNonNull(role.asString()).equals("Student")){ if (Objects.requireNonNull(role.asString()).equals("Student")) {
PrefUtils.storeIsTutor(getApplicationContext(), false); PrefUtils.storeIsTutor(getApplicationContext(), false);
}else{ } else {
PrefUtils.storeIsTutor(getApplicationContext(), true); PrefUtils.storeIsTutor(getApplicationContext(), true);
} }

View File

@ -119,7 +119,6 @@ public class MapActivity extends BaseActivity
mHandler.postDelayed(mStatusChecker, mInterval); mHandler.postDelayed(mStatusChecker, mInterval);
} }
}; };
@ -371,6 +370,8 @@ public class MapActivity extends BaseActivity
coordinateService.getOnlineCoordinates() coordinateService.getOnlineCoordinates()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.map(tutors -> Stream.of(tutors).
filterNot(t -> t.getDisplayMode().equals(SharingLevel.PRESENCE.toString())).toList())
.subscribeWith(new DisposableSingleObserver<List<Coordinate>>() { .subscribeWith(new DisposableSingleObserver<List<Coordinate>>() {
@Override @Override

View File

@ -69,12 +69,10 @@ public class TutorTab extends AppCompatActivity {
.subscribeWith(new DisposableSingleObserver<TutorTabViewModel>() { .subscribeWith(new DisposableSingleObserver<TutorTabViewModel>() {
@Override @Override
public void onSuccess(TutorTabViewModel tutorTabViewModel) { public void onSuccess(TutorTabViewModel tutorTabViewModel) {
// newTab = tutorTabViewModel;
// Log.d("TUTORTAB onsuc toPost",newTab.toString());
List<String> dutyHoursList = Stream.of(tutorTabViewModel.getDutyHours()) List<String> dutyHoursList = Stream.of(tutorTabViewModel.getDutyHours())
.map(DutyHourViewModel::getSummary).toList(); .map(DutyHourViewModel::getSummary).toList();
Log.d("TUTORTAB", Arrays.toString(dutyHoursList.toArray()));
userRoom.setText(String.format("%s: %s", getString(R.string.userRoom), tutorTabViewModel.getRoom())); userRoom.setText(String.format("%s: %s", getString(R.string.userRoom), tutorTabViewModel.getRoom()));
userEmail.setText(String.format("%s: %s", getString(R.string.userEmail), tutorTabViewModel.getEmailTutorTab())); userEmail.setText(String.format("%s: %s", getString(R.string.userEmail), tutorTabViewModel.getEmailTutorTab()));
if (!tutorTabViewModel.getNote().equals("")) { if (!tutorTabViewModel.getNote().equals("")) {
@ -115,10 +113,6 @@ public class TutorTab extends AppCompatActivity {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
newTab = new TutorTabViewModel(PrefUtils.getUserId(getApplicationContext()),note.getText().toString()); newTab = new TutorTabViewModel(PrefUtils.getUserId(getApplicationContext()),note.getText().toString());
Log.d("TUTORTAB button",note.getText().toString());
Log.d("TUTORTAB toPost",newTab.toString());
//topost jest nullem
// toPost.setNote(note.getText().toString());
putUserTab(newTab); putUserTab(newTab);
} }
}); });
@ -147,7 +141,6 @@ public class TutorTab extends AppCompatActivity {
} else { } else {
Toast.makeText(getApplicationContext(), Toast.makeText(getApplicationContext(),
"Network error " + error.getMessage(), Toast.LENGTH_SHORT).show(); "Network error " + error.getMessage(), Toast.LENGTH_SHORT).show();
Log.d("FEEDBACK", error.getMessage());
} }
} }
@ -158,7 +151,7 @@ public class TutorTab extends AppCompatActivity {
ResponseBody responseBody = ((HttpException) e).response().errorBody(); ResponseBody responseBody = ((HttpException) e).response().errorBody();
message = RestApiHelper.getErrorMessage(responseBody); message = RestApiHelper.getErrorMessage(responseBody);
} else { } else {
message = "Network Error !"; message = "Network Error!";
} }
// Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG) // Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG)

View File

@ -212,7 +212,7 @@ public class UsersListFragment extends Fragment {
private int sortByUserName(User t1, User t2) { private int sortByUserName(User t1, User t2) {
return t1.getFirstName().compareTo(t2.getFirstName()); return t1.getLastName().compareToIgnoreCase(t2.getLastName());
} }
private void showError(Throwable e) { private void showError(Throwable e) {

View File

@ -14,8 +14,8 @@ import io.swagger.annotations.ApiModelProperty;
*/ */
public class Coordinate extends BaseResponse { public class Coordinate extends BaseResponse {
Range<Double> latitudeRange = Range.create(52.466709, 52.467007); Range<Double> latitudeRange = Range.create(52.46598, 52.467545);
Range<Double> longtitudeRange = Range.create(16.926159, 16.926976); Range<Double> longtitudeRange = Range.create(16.926099, 16.927794);
@SerializedName("coordinateId") @SerializedName("coordinateId")
private UUID coordinateId = null; private UUID coordinateId = null;
@ -45,9 +45,9 @@ public class Coordinate extends BaseResponse {
private String label; private String label;
public Coordinate (Double latitude, Double longitude, Double altitude, String approximatedLocation, String label, String userId, String displayMode) { public Coordinate (Double latitude, Double longitude, Double altitude, String approximatedLocation, String label, String userId, String displayMode) {
//if (!latitudeRange.contains(latitude)) throw new IllegalArgumentException("Inappropriate latitude value" + latitude); if (!latitudeRange.contains(latitude)) throw new IllegalArgumentException("Inappropriate latitude value" + latitude);
//if (!longtitudeRange.contains(longitude)) throw new IllegalArgumentException("Inappropriate longitude value" + longitude); if (!longtitudeRange.contains(longitude)) throw new IllegalArgumentException("Inappropriate longitude value" + longitude);
//if (approximatedLocation == null) throw new IllegalArgumentException("Inappropriate approximatedLocation"); if (approximatedLocation == null) throw new IllegalArgumentException("Inappropriate approximatedLocation");
this.latitude = latitude; this.latitude = latitude;
this.longitude = longitude; this.longitude = longitude;

View File

@ -38,8 +38,6 @@ import com.uam.wmi.findmytutor.utils.mapUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
@ -50,7 +48,7 @@ import timber.log.Timber;
import static com.uam.wmi.findmytutor.utils.Consts.presenceApproximatedName; import static com.uam.wmi.findmytutor.utils.Consts.presenceApproximatedName;
import static com.uam.wmi.findmytutor.utils.Consts.presenceLatitude; import static com.uam.wmi.findmytutor.utils.Consts.presenceLatitude;
import static com.uam.wmi.findmytutor.utils.Consts.presencelongitude; import static com.uam.wmi.findmytutor.utils.Consts.presenceLongitude;
import static com.uam.wmi.findmytutor.utils.PrefUtils.storeBackgroundLocationStatus; import static com.uam.wmi.findmytutor.utils.PrefUtils.storeBackgroundLocationStatus;
public class BackgroundLocalizationService extends Service { public class BackgroundLocalizationService extends Service {
@ -58,20 +56,27 @@ public class BackgroundLocalizationService extends Service {
private static final String TAG = "MyLocationService"; private static final String TAG = "MyLocationService";
private static final int LOCATION_INTERVAL = 1000; private static final int LOCATION_INTERVAL = 1000;
private static final float LOCATION_DISTANCE = 5f; private static final float LOCATION_DISTANCE = 5f;
private static long notify_interval = 10000;
Location mLastLocation;
Boolean stopService = false;
Intent intent;
public static String str_receiver = "background.location.broadcast"; public static String str_receiver = "background.location.broadcast";
ArrayList<String> providers = new ArrayList<String>(); private static long notify_interval = 20000;
LocationListener[] mLocationListeners;
private LocationManager mLocationManager = null; private LocationManager mLocationManager = null;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private HandlerThread mHandlerThread = null; private HandlerThread mHandlerThread = null;
private Runnable mStatusChecker; private Runnable mStatusChecker;
private FusedLocationProviderClient mFusedLocationClient; private FusedLocationProviderClient mFusedLocationClient;
private Double latitude;
private Double longitude;
private Double altitude;
private String approximatedBuildingPart = null;
private CompositeDisposable disposable = new CompositeDisposable();
private CoordinateService coordinateService = null;
private Location mLastLocation;
private Boolean stopService = false;
private Intent intent;
private ArrayList<String> providers = new ArrayList<String>();
private LocationListener[] mLocationListeners;
private ApproximatedLocalization approximatedLocalization;
public BackgroundLocalizationService() { public BackgroundLocalizationService() {
providers.add(LocationManager.GPS_PROVIDER); providers.add(LocationManager.GPS_PROVIDER);
@ -112,6 +117,11 @@ public class BackgroundLocalizationService extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
Log.e(TAG, "onCreate"); Log.e(TAG, "onCreate");
approximatedLocalization = new ApproximatedLocalization(mapUtils.loadJsonFromAsset(getApplicationContext(), "building.geojson"));
coordinateService = ApiClient.getClient(getApplicationContext())
.create(CoordinateService.class);
storeBackgroundLocationStatus(getApplication(), true); storeBackgroundLocationStatus(getApplication(), true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
@ -148,22 +158,21 @@ public class BackgroundLocalizationService extends Service {
if (!stopService) { if (!stopService) {
Timer mTimer = new Timer(); mStatusChecker = () -> {
mTimer.schedule(new TimerTaskToGetLocation(), 20, notify_interval); try {
intent = new Intent(str_receiver); fn_getlocation();
} finally {
mHandler.postDelayed(mStatusChecker, notify_interval);
}
};
fn_getlocation();
AsyncTask.execute(mStatusChecker);
} }
} }
private class TimerTaskToGetLocation extends TimerTask {
@Override
public void run() {
mHandler.post(BackgroundLocalizationService.this::fn_getlocation);
}
}
@RequiresApi(api = Build.VERSION_CODES.O) @RequiresApi(api = Build.VERSION_CODES.O)
private void startMyOwnForeground() { private void startMyOwnForeground() {
@ -225,8 +234,68 @@ public class BackgroundLocalizationService extends Service {
}); });
} }
@SuppressLint("LongLogTag")
private void fn_update(Location location) { private void fn_update(Location location) {
new Task(location).execute(); Double latitude = location.getLatitude();
Double longitude = location.getLongitude();
Double altitude = location.getAltitude();
String locationLevel = PrefUtils.getLocationLevel(getApplicationContext());
approximatedBuildingPart = approximatedLocalization.getNameOfBuildingPart(Point.fromLngLat(longitude, latitude));
if (locationLevel.equals(SharingLevel.PRESENCE.toString())) {
latitude = presenceLatitude;
longitude = presenceLongitude;
approximatedBuildingPart = presenceApproximatedName;
} else if (locationLevel.equals(SharingLevel.APPROXIMATED.toString())) {
List<Double> points = approximatedLocalization.getMiddlePointOfBuildingPart(approximatedBuildingPart);
latitude = points.get(0);
longitude = points.get(1);
} else if (locationLevel.equals(SharingLevel.MANUAL.toString())) {
LatLng latLng = PrefUtils.getManualLocation(getApplicationContext());
latitude = latLng.getLatitude();
longitude = latLng.getLongitude();
}
try {
Coordinate coordinate = new Coordinate(
latitude,
longitude,
altitude,
approximatedBuildingPart,
(PrefUtils.isStatusEnabled(getApplicationContext())) ? PrefUtils.getUserStatus(getApplicationContext()) : "",
PrefUtils.getUserId(getApplicationContext()),
locationLevel
);
disposable.add(
coordinateService
.postCoordinate(coordinate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Coordinate>() {
@SuppressLint("LongLogTag")
@Override
public void onSuccess(Coordinate coord) {
Log.e("CoordinateService onSuccess", String.valueOf(coord));
}
@SuppressLint("LongLogTag")
@Override
public void onError(Throwable e) {
Log.e("CoordinateService onError", e.getMessage());
if (e instanceof HttpException) {
ResponseBody responseBody = ((HttpException) e).response().errorBody();
Log.e("CoordinateService onError", RestApiHelper.getErrorMessage(responseBody));
}
}
}));
} catch (IllegalArgumentException e) {
Log.e("CoordinateService onError",e.toString());
}
} }
@Override @Override
@ -288,86 +357,4 @@ public class BackgroundLocalizationService extends Service {
Log.e(TAG, "onStatusChanged: " + provider); Log.e(TAG, "onStatusChanged: " + provider);
} }
} }
@SuppressLint("StaticFieldLeak")
private class Task extends AsyncTask {
ApproximatedLocalization approximatedLocalization;
private Double latitude;
private Double longitude;
private Double altitude;
private String approximatedBuildingPart = null;
private CompositeDisposable disposable = new CompositeDisposable();
private CoordinateService coordinateService = ApiClient.getClient(getApplicationContext())
.create(CoordinateService.class);
private Task(Location location) {
latitude = location.getLatitude();
longitude = location.getLongitude();
altitude = location.getAltitude();
approximatedLocalization = new ApproximatedLocalization(mapUtils.loadJsonFromAsset(getApplicationContext(), "building.geojson"));
approximatedBuildingPart = approximatedLocalization.getNameOfBuildingPart(Point.fromLngLat(longitude, latitude));
}
@Override
protected Object doInBackground(Object[] objects) {
String locationLevel = PrefUtils.getLocationLevel(getApplicationContext());
if (locationLevel.equals(SharingLevel.PRESENCE.toString())) {
latitude = presenceLatitude;
longitude = presencelongitude;
approximatedBuildingPart = presenceApproximatedName;
} else if (locationLevel.equals(SharingLevel.APPROXIMATED.toString())) {
List<Double> points = approximatedLocalization.getMiddlePointOfBuildingPart(approximatedBuildingPart);
latitude = points.get(0);
longitude = points.get(1);
} else if (locationLevel.equals(SharingLevel.MANUAL.toString())) {
LatLng latLng = PrefUtils.getManualLocation(getApplicationContext());
latitude = latLng.getLatitude();
longitude = latLng.getLongitude();
}
try {
Coordinate coordinate = new Coordinate(
latitude,
longitude,
altitude,
approximatedBuildingPart,
(PrefUtils.isStatusEnabled(getApplicationContext())) ? PrefUtils.getUserStatus(getApplicationContext()) : "",
PrefUtils.getUserId(getApplicationContext()),
locationLevel
);
disposable.add(
coordinateService
.postCoordinate(coordinate)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Coordinate>() {
@SuppressLint("LongLogTag")
@Override
public void onSuccess(Coordinate coord) {
Log.e("CoordinateService onSuccess", String.valueOf(coord));
}
@SuppressLint("LongLogTag")
@Override
public void onError(Throwable e) {
Log.e("CoordinateService onError", e.getMessage());
if (e instanceof HttpException) {
ResponseBody responseBody = ((HttpException) e).response().errorBody();
Log.e("CoordinateService onError", RestApiHelper.getErrorMessage(responseBody));
}
}
}));
} catch (IllegalArgumentException e) {
Timber.e(String.valueOf(e));
}
return null;
}
}
} }

View File

@ -1,7 +1,12 @@
package com.uam.wmi.findmytutor.utils; package com.uam.wmi.findmytutor.utils;
import android.util.Range;
public class Consts { public class Consts {
public final static Double presenceLatitude = 65.600244; public final static Double presenceLatitude = 52.466365;
public final static Double presencelongitude = 480.032153; public final static Double presenceLongitude = 16.926792;
public final static String presenceApproximatedName = "unknown"; public final static String presenceApproximatedName = "unknown";
public final static Range<Double> latitudeRange = Range.create(52.466709, 52.467007);
public final static Range<Double> longtitudeRange = Range.create(16.926159, 16.926976);
} }

View File

@ -3,6 +3,7 @@ package com.uam.wmi.findmytutor.utils;
import android.animation.TypeEvaluator; import android.animation.TypeEvaluator;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.location.Location;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -18,10 +19,14 @@ import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.layers.FillLayer; import com.mapbox.mapboxsdk.style.layers.FillLayer;
import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.Layer;
import com.uam.wmi.findmytutor.model.Coordinate;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import static com.uam.wmi.findmytutor.utils.Consts.latitudeRange;
import static com.uam.wmi.findmytutor.utils.Consts.longtitudeRange;
public class mapUtils { public class mapUtils {
// Boundires // Boundires
@ -73,6 +78,10 @@ public class mapUtils {
} }
} }
public static Boolean checkIfCoordinateIsValid(Location coordinate){
return (!latitudeRange.contains(coordinate.getLatitude()) && !longtitudeRange.contains(coordinate.getLongitude()));
}
// Function for marker animation // Function for marker animation
public static class LatLngEvaluator implements TypeEvaluator<LatLng> { public static class LatLngEvaluator implements TypeEvaluator<LatLng> {
// Method is used to interpolate the marker animation. // Method is used to interpolate the marker animation.

View File

@ -147,7 +147,7 @@
<string name="remove_manual_location">Usuń manualną lokację</string> <string name="remove_manual_location">Usuń manualną lokację</string>
<string name="title_activity_tutor_tab">Profil</string> <string name="title_activity_tutor_tab">Profil</string>
<string name="saveButton">Zapisz</string> <string name="saveButton">Zapisz</string>
<string name="tutorTabHint">Tutaj możesz dodać swoją notatkę.\\nBędzie widoczna dla innych użytkowników.</string> <string name="tutorTabHint">Tutaj możesz dodać swoją notatkę. Będzie widoczna dla innych użytkowników.</string>
<string name="modal_location_send">WYŚLIJ</string> <string name="modal_location_send">WYŚLIJ</string>
<string name="modal_location_hint">Proszę nazwać wybraną lokację.</string> <string name="modal_location_hint">Proszę nazwać wybraną lokację.</string>
</resources> </resources>

View File

@ -83,7 +83,7 @@
<string name="settings_description">Descrition</string> <string name="settings_description">Descrition</string>
<string name="saveButton">Save</string> <string name="saveButton">Save</string>
<string name="tutorTabHint">Here you can add your note.\nIt will be visible to the other users.</string> <string name="tutorTabHint">Here you can add your note. It will be visible to the other users.</string>
<string name="key_description">key_description</string> <string name="key_description">key_description</string>