diff --git a/app/build.gradle b/app/build.gradle index 16a2e6b3..eecb4212 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,8 +30,8 @@ android { applicationId "com.HMSolutions.thikrallah" minSdkVersion 21 targetSdkVersion 31 - versionCode 120 - versionName "6.0.0" + versionCode 121 + versionName "6.1.0" } lintOptions { disable 'MissingTranslation' @@ -62,10 +62,10 @@ repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://maven.google.com" } + } dependencies { - implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.4.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f2e5d1a5..18b38926 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ - + diff --git a/app/src/main/java/com/HMSolutions/thikrallah/Fragments/MainFragment.java b/app/src/main/java/com/HMSolutions/thikrallah/Fragments/MainFragment.java index 7b224ee3..2480ac4f 100755 --- a/app/src/main/java/com/HMSolutions/thikrallah/Fragments/MainFragment.java +++ b/app/src/main/java/com/HMSolutions/thikrallah/Fragments/MainFragment.java @@ -23,6 +23,7 @@ import com.HMSolutions.thikrallah.hisnulmuslim.DuaGroupActivity; import com.HMSolutions.thikrallah.quran.labs.androidquran.QuranDataActivity; + import java.util.Locale; public class MainFragment extends Fragment { @@ -69,6 +70,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Button button_quran = (Button) view.findViewById(R.id.button_quran); Button button_hisn_almuslim = (Button) view.findViewById(R.id.hisn_almuslim); Button button_athan = (Button) view.findViewById(R.id.button_athan); + Button button_qibla = (Button) view.findViewById(R.id.button_qibla); + button_athan.setOnClickListener(new OnClickListener() { @Override @@ -77,6 +80,16 @@ public void onClick(View v) { } + }); + button_qibla.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + mCallback.launchFragment(new QiblaFragment(), new Bundle(), "QiblaFragment"); + + + } + }); mPrefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity()); diff --git a/app/src/main/java/com/HMSolutions/thikrallah/Fragments/QiblaFragment.java b/app/src/main/java/com/HMSolutions/thikrallah/Fragments/QiblaFragment.java new file mode 100644 index 00000000..2cbedf4c --- /dev/null +++ b/app/src/main/java/com/HMSolutions/thikrallah/Fragments/QiblaFragment.java @@ -0,0 +1,251 @@ +package com.HMSolutions.thikrallah.Fragments; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.RotateAnimation; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; + +import com.HMSolutions.thikrallah.MainActivity; +import com.HMSolutions.thikrallah.R; +import com.HMSolutions.thikrallah.Utilities.CustomLocation; +import com.HMSolutions.thikrallah.Utilities.MainInterface; +import com.HMSolutions.thikrallah.compass.Compass; +import com.HMSolutions.thikrallah.compass.SOTWFormatter; + + + +public class QiblaFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { + private MainInterface mCallback; + private SharedPreferences.OnSharedPreferenceChangeListener prefListener; + private CheckBox is_Manual_Location; + private TextView currentLocation; + private static final String TAG = "CompassActivity"; + private Compass compass; + private ImageView arrowView; + private ImageView dialView; + private TextView sotwLabel; // SOTW is for "side of the world" + private Context mContext; + private float currentAzimuth; + private float currentQibla; + private SOTWFormatter sotwFormatter; + + // private TextView locationDescription; + + + public QiblaFragment() { + } + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equalsIgnoreCase("latitude") || key.equalsIgnoreCase("longitude") + || key.equalsIgnoreCase("isCustomLocation")||key.equalsIgnoreCase("c_latitude") + ||key.equalsIgnoreCase("c_longitude")) { + if (this.getView()!=null){ + updateQiblaDirection(); + is_Manual_Location.setChecked(PreferenceManager.getDefaultSharedPreferences(this.getContext()).getBoolean("isCustomLocation",false)); + } + + } + + } + + private void updateQiblaDirection() { + this.calculateQiblaDirection(); + currentLocation.setText(this.getContext().getResources().getString(R.string.current_location) + +MainActivity.getLatitude(getContext())+"; "+ MainActivity.getLongitude(getContext())); + } + + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext=context; + MainActivity.setLocale(context); + try { + prefListener = this; + mCallback = (MainInterface) context; + mCallback.requestLocationUpdate(); + + + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement MainInterface"); + } + Log.d(TAG, "start compass"); + + + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mContext=this.getActivity(); + MainActivity.setLocale(this.getContext()); + ((AppCompatActivity) this.getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true); + ((AppCompatActivity) this.getActivity()).getSupportActionBar().setDisplayShowHomeEnabled(true); + this.setHasOptionsMenu(true); + + View view = inflater.inflate(R.layout.compass_fragment, container, + false); + + sotwFormatter = new SOTWFormatter(this.getActivity().getApplicationContext()); + + arrowView = view.findViewById(R.id.main_image_hands); + dialView = view.findViewById(R.id.main_image_dial); + setupCompass(); + //locationDescription=(TextView) view.findViewById(R.id.textView_location); + + is_Manual_Location= (CheckBox) view.findViewById(R.id.is_manual_location); + if (PreferenceManager.getDefaultSharedPreferences(this.getContext()).getBoolean("isCustomLocation", false)) { + is_Manual_Location.setChecked(true); + }else{ + is_Manual_Location.setChecked(false); + } + + is_Manual_Location.setOnClickListener(this); + currentLocation= view.findViewById(R.id.current_location); + this.updateQiblaDirection(); + PreferenceManager.getDefaultSharedPreferences(this.getContext()).registerOnSharedPreferenceChangeListener(prefListener); + return view; + } + + private void setupCompass() { + compass = new Compass(this.getActivity().getApplicationContext()); + Compass.CompassListener cl = getCompassListener(); + compass.setListener(cl); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId()==android.R.id.home) { + // Respond to the action bar's Up/Home button + this.getActivity().onBackPressed(); + return true; + } + return false; + } + private Compass.CompassListener getCompassListener() { + return new Compass.CompassListener() { + @Override + public void onNewAzimuth(final float azimuth) { + // UI updates only in UI thread + // https://stackoverflow.com/q/11140285/444966 + + ((MainActivity)mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + adjustArrow(azimuth); + adjustSotwLabel(azimuth); + } + }); + } + }; + } + + private void adjustArrow(float azimuth) { + Animation an = new RotateAnimation(-currentAzimuth, -azimuth, + Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, + 0.5f); + float newQibla=(float)this.calculateQiblaDirection(); + Animation an2 = new RotateAnimation(currentQibla-currentAzimuth, newQibla-azimuth, + Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, + 0.5f); + + currentQibla=newQibla; + currentAzimuth = azimuth; + + an.setDuration(500); + an.setRepeatCount(0); + an.setFillAfter(true); + dialView.startAnimation(an); + + + an2.setDuration(500); + an2.setRepeatCount(0); + an2.setFillAfter(true); + + arrowView.startAnimation(an2); + } + + private void adjustSotwLabel(float azimuth) { + sotwLabel.setText(sotwFormatter.format(azimuth)); + } + + @Override + public void onPause(){ + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity().getApplicationContext()); + prefs.unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + compass.stop(); + } + + @Override + public void onResume() { + super.onResume(); + PreferenceManager.getDefaultSharedPreferences(this.getContext()).registerOnSharedPreferenceChangeListener(prefListener); + logScreen(); + this.updateQiblaDirection(); + Log.d(TAG, "start compass"); + compass.start(); + } + + private void logScreen() { + /* + Bundle bundle = new Bundle(); + bundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, this.getClass().getSimpleName()); + bundle.putString(FirebaseAnalytics.Param.SCREEN_CLASS, this.getClass().getSimpleName()); + FirebaseAnalytics.getInstance(this.getActivity()).logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle); + */ + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + @Override + public void onClick(View v) { + if (is_Manual_Location.isChecked()){ + CustomLocation Customlocation=new CustomLocation(this.getActivity()); + Customlocation.show(); + }else{ + PreferenceManager.getDefaultSharedPreferences(this.getContext()).edit().putBoolean("isCustomLocation", false).commit(); + } + this.updateQiblaDirection(); + } + + /** + * qibla direction in degrees from the north (clock-wise) + * @return number - 0 means north, 90 means east, 270 means west, etc + * + */ + public double calculateQiblaDirection(){ + double latitude = Double.parseDouble(MainActivity.getLatitude(getContext())); + double longitude = Double.parseDouble(MainActivity.getLongitude(getContext())); + double lng_a = 39.82616111; + double lat_a = 21.42250833; + double deg = Math.toDegrees(Math.atan2(Math.sin(Math.toRadians(lng_a-longitude)), + Math.cos(Math.toRadians(latitude))*Math.tan(Math.toRadians(lat_a)) + -Math.sin(Math.toRadians(latitude))*Math.cos(Math.toRadians(lng_a-longitude)))); + if (deg>=0){ + return deg; + }else{ + return deg+360; + } + } +} diff --git a/app/src/main/java/com/HMSolutions/thikrallah/MainActivity.java b/app/src/main/java/com/HMSolutions/thikrallah/MainActivity.java index 97073604..86bb2967 100755 --- a/app/src/main/java/com/HMSolutions/thikrallah/MainActivity.java +++ b/app/src/main/java/com/HMSolutions/thikrallah/MainActivity.java @@ -375,7 +375,8 @@ private void requestPermissions(){ Log.d(TAG,"forground_service permission requested"); } } - + int alarmsPermission = ContextCompat.checkSelfPermission(this, + Manifest.permission.SCHEDULE_EXACT_ALARM); int mediacontrolPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL); @@ -390,6 +391,9 @@ private void requestPermissions(){ if (mediacontrolPermission != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.MEDIA_CONTENT_CONTROL); } + if (alarmsPermission != PackageManager.PERMISSION_GRANTED) { + listPermissionsNeeded.add(Manifest.permission.SCHEDULE_EXACT_ALARM); + } if (locationPermission != PackageManager.PERMISSION_GRANTED) { listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION); @@ -431,10 +435,6 @@ private void requestPermissions(){ new String[]{Manifest.permission.MEDIA_CONTENT_CONTROL}, 1); } - - - - } @Override diff --git a/app/src/main/java/com/HMSolutions/thikrallah/compass/Compass.java b/app/src/main/java/com/HMSolutions/thikrallah/compass/Compass.java new file mode 100644 index 00000000..e6a3ba26 --- /dev/null +++ b/app/src/main/java/com/HMSolutions/thikrallah/compass/Compass.java @@ -0,0 +1,111 @@ +package com.HMSolutions.thikrallah.compass; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class Compass implements SensorEventListener { + private static final String TAG = "Compass"; + + public interface CompassListener { + void onNewAzimuth(float azimuth); + } + + private CompassListener listener; + + private SensorManager sensorManager; + private Sensor gsensor; + private Sensor msensor; + + private float[] mGravity = new float[3]; + private float[] mGeomagnetic = new float[3]; + private float[] R = new float[9]; + private float[] I = new float[9]; + + private float azimuth; + private float azimuthFix; + + public Compass(Context context) { + sensorManager = (SensorManager) context + .getSystemService(Context.SENSOR_SERVICE); + gsensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + msensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + } + + public void start() { + sensorManager.registerListener(this, gsensor, + SensorManager.SENSOR_DELAY_GAME); + sensorManager.registerListener(this, msensor, + SensorManager.SENSOR_DELAY_GAME); + } + + public void stop() { + sensorManager.unregisterListener(this); + } + + public void setAzimuthFix(float fix) { + azimuthFix = fix; + } + + public void resetAzimuthFix() { + setAzimuthFix(0); + } + + public void setListener(CompassListener l) { + listener = l; + } + + @Override + public void onSensorChanged(SensorEvent event) { + final float alpha = 0.97f; + + synchronized (this) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + mGravity[0] = alpha * mGravity[0] + (1 - alpha) + * event.values[0]; + mGravity[1] = alpha * mGravity[1] + (1 - alpha) + * event.values[1]; + mGravity[2] = alpha * mGravity[2] + (1 - alpha) + * event.values[2]; + + // mGravity = event.values; + + // Log.e(TAG, Float.toString(mGravity[0])); + } + + if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + // mGeomagnetic = event.values; + + mGeomagnetic[0] = alpha * mGeomagnetic[0] + (1 - alpha) + * event.values[0]; + mGeomagnetic[1] = alpha * mGeomagnetic[1] + (1 - alpha) + * event.values[1]; + mGeomagnetic[2] = alpha * mGeomagnetic[2] + (1 - alpha) + * event.values[2]; + // Log.e(TAG, Float.toString(event.values[0])); + + } + + boolean success = SensorManager.getRotationMatrix(R, I, mGravity, + mGeomagnetic); + if (success) { + float orientation[] = new float[3]; + SensorManager.getOrientation(R, orientation); + // Log.d(TAG, "azimuth (rad): " + azimuth); + azimuth = (float) Math.toDegrees(orientation[0]); // orientation + azimuth = (azimuth + azimuthFix + 360) % 360; + // Log.d(TAG, "azimuth (deg): " + azimuth); + if (listener != null) { + listener.onNewAzimuth(azimuth); + } + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } +} diff --git a/app/src/main/java/com/HMSolutions/thikrallah/compass/SOTWFormatter.java b/app/src/main/java/com/HMSolutions/thikrallah/compass/SOTWFormatter.java new file mode 100644 index 00000000..f9cd281e --- /dev/null +++ b/app/src/main/java/com/HMSolutions/thikrallah/compass/SOTWFormatter.java @@ -0,0 +1,108 @@ +package com.HMSolutions.thikrallah.compass; + +import android.content.Context; + +import com.HMSolutions.thikrallah.R; + +/** + * SOTW is short Side Of The World + * + * The class helps to convert azimuth degrees to human readable + * label like "242° SW" or "0° N" + * + * This is a task of finding the closest element in the array. + * Binary search is used to save some CPU. + * + * Copied with modifications from + * https://www.geeksforgeeks.org/find-closest-number-array/ + */ +public class SOTWFormatter { + private static final int[] sides = {0, 45, 90, 135, 180, 225, 270, 315, 360}; + private static String[] names = null; + + public SOTWFormatter(Context context) { + initLocalizedNames(context); + } + + public String format(float azimuth) { + int iAzimuth = (int)azimuth; + int index = findClosestIndex(iAzimuth); + return iAzimuth + "° " + names[index]; + } + + private void initLocalizedNames(Context context) { + // it will be localized version of + // {"N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"} + // yes, N is twice, for 0 and for 360 + + if (names == null) { + names = new String[]{ + context.getString(R.string.sotw_north), + context.getString(R.string.sotw_northeast), + context.getString(R.string.sotw_east), + context.getString(R.string.sotw_southeast), + context.getString(R.string.sotw_south), + context.getString(R.string.sotw_southwest), + context.getString(R.string.sotw_west), + context.getString(R.string.sotw_northwest), + context.getString(R.string.sotw_north) + }; + } + } + + /** + * Finds index of the closest element to identify Side Of The World label + * @param target + * @return index of the closest element + */ + private static int findClosestIndex(int target) { + // in the original binary search https://www.geeksforgeeks.org/find-closest-number-array/ + // you will see more steps to reduce the time + // in in this particular case the corner conditions are never true + // e.g. azimuth is never negative, so there is no point to check + // these conditions. Also we don't check if target is equal to element of array, + // because most of the time it's not. + + // and the main difference is it finds the index, not the value + + // Doing binary search + int i = 0, j = sides.length, mid = 0; + while (i < j) { + mid = (i + j) / 2; + + /* If target is less than array element, + then search in left */ + if (target < sides[mid]) { + + // If target is greater than previous + // to mid, return closest of two + if (mid > 0 && target > sides[mid - 1]) { + return getClosest(mid - 1, mid, target); + } + + /* Repeat for left half */ + j = mid; + } else { + if (mid < sides.length-1 && target < sides[mid + 1]) { + return getClosest(mid, mid + 1, target); + } + i = mid + 1; // update i + } + } + + // Only single element left after search + return mid; + } + + // Method to compare which one is the more close + // We find the closest by taking the difference + // between the target and both values. It assumes + // that val2 is greater than val1 and target lies + // between these two. + private static int getClosest(int index1, int index2, int target) { + if (target - sides[index1] >= sides[index2] - target) { + return index2; + } + return index1; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/ic_needle_1.png b/app/src/main/res/drawable-hdpi/ic_needle_1.png new file mode 100644 index 00000000..2a140efe Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_needle_1.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_needle_2.png b/app/src/main/res/drawable-hdpi/ic_needle_2.png new file mode 100644 index 00000000..0237d3f7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_needle_2.png differ diff --git a/app/src/main/res/drawable-hdpi/qibla.png b/app/src/main/res/drawable-hdpi/qibla.png new file mode 100644 index 00000000..27cae2f6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/qibla.png differ diff --git a/app/src/main/res/drawable-mdpi/qibla.png b/app/src/main/res/drawable-mdpi/qibla.png new file mode 100644 index 00000000..0aaa2de0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/qibla.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_needle_1.png b/app/src/main/res/drawable-xhdpi/ic_needle_1.png new file mode 100644 index 00000000..eae3e9c5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_needle_1.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_needle_2.png b/app/src/main/res/drawable-xhdpi/ic_needle_2.png new file mode 100644 index 00000000..2044b1b4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_needle_2.png differ diff --git a/app/src/main/res/drawable-xhdpi/qibla.png b/app/src/main/res/drawable-xhdpi/qibla.png new file mode 100644 index 00000000..2bf4e292 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/qibla.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_needle_1.png b/app/src/main/res/drawable-xxhdpi/ic_needle_1.png new file mode 100644 index 00000000..45d4fd34 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_needle_1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_needle_2.png b/app/src/main/res/drawable-xxhdpi/ic_needle_2.png new file mode 100644 index 00000000..cbe045cb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_needle_2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/qibla.png b/app/src/main/res/drawable-xxhdpi/qibla.png new file mode 100644 index 00000000..de40c740 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/qibla.png differ diff --git a/app/src/main/res/drawable/dial.png b/app/src/main/res/drawable/dial.png new file mode 100644 index 00000000..11c2c349 Binary files /dev/null and b/app/src/main/res/drawable/dial.png differ diff --git a/app/src/main/res/drawable/hands.png b/app/src/main/res/drawable/hands.png new file mode 100644 index 00000000..1d24f524 Binary files /dev/null and b/app/src/main/res/drawable/hands.png differ diff --git a/app/src/main/res/layout/compass_fragment.xml b/app/src/main/res/layout/compass_fragment.xml new file mode 100644 index 00000000..8567b37b --- /dev/null +++ b/app/src/main/res/layout/compass_fragment.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 6be9ef07..ffcc7d07 100755 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -10,8 +10,7 @@ tools:context="com.HMSolutions.thikrallah.MainActivity"> +