MainActivity.java (13145B)
1 package com.github.cythara; 2 3 import android.Manifest; 4 import android.content.Intent; 5 import android.content.SharedPreferences; 6 import android.content.pm.PackageManager; 7 import android.graphics.Insets; 8 import android.net.Uri; 9 import android.os.Build; 10 import android.os.Bundle; 11 import android.view.ContextThemeWrapper; 12 import android.view.Menu; 13 import android.view.MenuItem; 14 import android.view.ViewGroup; 15 import android.view.WindowManager.LayoutParams; 16 17 import com.github.cythara.ListenerFragment.TaskCallbacks; 18 import com.jaredrummler.materialspinner.MaterialSpinner; 19 import com.jaredrummler.materialspinner.MaterialSpinner.OnItemSelectedListener; 20 import com.jaredrummler.materialspinner.MaterialSpinnerAdapter; 21 import com.shawnlin.numberpicker.NumberPicker; 22 import com.shawnlin.numberpicker.NumberPicker.OnValueChangeListener; 23 24 import java.util.Arrays; 25 26 import androidx.annotation.NonNull; 27 import androidx.appcompat.app.AlertDialog; 28 import androidx.appcompat.app.AlertDialog.Builder; 29 import androidx.appcompat.app.AppCompatActivity; 30 import androidx.appcompat.app.AppCompatDelegate; 31 import androidx.appcompat.widget.Toolbar; 32 import androidx.core.app.ActivityCompat; 33 import androidx.core.content.ContextCompat; 34 import androidx.core.view.ViewCompat; 35 import androidx.core.view.WindowInsetsCompat; 36 import androidx.fragment.app.FragmentManager; 37 38 public class MainActivity extends AppCompatActivity implements TaskCallbacks, 39 OnItemSelectedListener, OnValueChangeListener { 40 41 public static final int RECORD_AUDIO_PERMISSION = 0; 42 public static final String PREFS_FILE = "prefs_file"; 43 public static final String USE_SCIENTIFIC_NOTATION = "use_scientific_notation"; 44 public static final String CURRENT_TUNING = "current_tuning"; 45 protected static final String REFERENCE_PITCH = "reference_pitch"; 46 private static final String TAG_LISTENER_FRAGMENT = "listener_fragment"; 47 private static final String USE_DARK_MODE = "use_dark_mode"; 48 private static int tuningPosition = 0; 49 private static boolean isDarkModeEnabled; 50 private static int referencePitch; 51 private static int referencePosition; 52 private static boolean isAutoModeEnabled = true; 53 54 public static Tuning getCurrentTuning() { 55 return TuningMapper.getTuningFromPosition(tuningPosition); 56 } 57 58 public static boolean isDarkModeEnabled() { 59 return isDarkModeEnabled; 60 } 61 62 public static int getReferencePitch() { 63 return referencePitch; 64 } 65 66 public static boolean isAutoModeEnabled() { 67 return isAutoModeEnabled; 68 } 69 70 public static int getReferencePosition() { 71 return referencePosition - 1; //to account for the position of the AUTO option 72 } 73 74 @Override 75 protected void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 78 79 int permissionCheck = ContextCompat.checkSelfPermission(this, 80 Manifest.permission.RECORD_AUDIO); 81 82 if (permissionCheck != PackageManager.PERMISSION_GRANTED) { 83 requestRecordAudioPermission(); 84 } else { 85 startRecording(); 86 } 87 88 enableTheme(); 89 90 setContentView(R.layout.activity_main); 91 92 setTuning(); 93 setReferencePitch(); 94 95 getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); 96 97 Toolbar myToolbar = findViewById(R.id.my_toolbar); 98 myToolbar.setTitle(R.string.app_name); 99 myToolbar.showOverflowMenu(); 100 setSupportActionBar(myToolbar); 101 102 103 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 104 ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main_layout), (v, windowInsets) -> { 105 Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).toPlatformInsets(); 106 // Apply the insets as a margin to the view. This solution sets only the 107 // bottom, left, and right dimensions, but you can apply whichever insets are 108 // appropriate to your layout. You can also update the view padding if that's 109 // more appropriate. 110 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); 111 mlp.leftMargin = insets.left; 112 mlp.bottomMargin = insets.bottom; 113 mlp.rightMargin = insets.right; 114 mlp.topMargin = insets.top; 115 v.setLayoutParams(mlp); 116 117 // Return CONSUMED if you don't want the window insets to keep passing 118 // down to descendant views. 119 return WindowInsetsCompat.CONSUMED; 120 }); 121 } 122 } 123 124 @Override 125 public boolean onCreateOptionsMenu(Menu menu) { 126 getMenuInflater().inflate(R.menu.toolbar_menu, menu); 127 return true; 128 } 129 130 @Override 131 public boolean onOptionsItemSelected(MenuItem item) { 132 int itemId = item.getItemId(); 133 if (itemId == R.id.show_privacy_policy) { 134 Intent browserIntent = new Intent(Intent.ACTION_VIEW, 135 Uri.parse(getString(R.string.privacy_policy_link))); 136 startActivity(browserIntent); 137 } else if (itemId == R.id.set_notation) { 138 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 139 MODE_PRIVATE); 140 final boolean useScientificNotation = 141 preferences.getBoolean(USE_SCIENTIFIC_NOTATION, true); 142 143 int checkedItem = useScientificNotation ? 0 : 1; 144 145 Builder builder = new Builder(new ContextThemeWrapper(this, 146 R.style.AppTheme)); 147 builder.setTitle(R.string.choose_notation); 148 builder.setSingleChoiceItems(R.array.notations, checkedItem, 149 (dialog, which) -> { 150 SharedPreferences.Editor editor = preferences.edit(); 151 editor.putBoolean(USE_SCIENTIFIC_NOTATION, which == 0); 152 editor.apply(); 153 154 dialog.dismiss(); 155 156 TunerView tunerView = findViewById(R.id.pitch); 157 tunerView.invalidate(); 158 }); 159 builder.show(); 160 } else if (itemId == R.id.toggle_dark_mode) { 161 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 162 MODE_PRIVATE); 163 boolean currentlyUsingDarkMode = preferences.getBoolean(USE_DARK_MODE, true); 164 165 SharedPreferences.Editor editor = preferences.edit(); 166 editor.putBoolean(USE_DARK_MODE, !currentlyUsingDarkMode); 167 editor.apply(); 168 169 recreate(); 170 } else if (itemId == R.id.set_reference_pitch) { 171 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 172 MODE_PRIVATE); 173 int referencePitch = preferences.getInt(REFERENCE_PITCH, 440); 174 175 NumberPickerDialog dialog = new NumberPickerDialog(); 176 177 Bundle bundle = new Bundle(); 178 bundle.putInt("current_value", referencePitch); 179 dialog.setArguments(bundle); 180 181 dialog.setValueChangeListener(this); 182 dialog.show(getSupportFragmentManager(), "reference_pitch_picker"); 183 } else if (itemId == R.id.choose_tuning_mode) { 184 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 185 MODE_PRIVATE); 186 NotePickerDialog dialog = new NotePickerDialog(); 187 188 Bundle bundle = new Bundle(); 189 bundle.putBoolean("use_scientific_notation", preferences.getBoolean( 190 MainActivity.USE_SCIENTIFIC_NOTATION, true)); 191 bundle.putInt("current_value", referencePosition); 192 dialog.setArguments(bundle); 193 194 dialog.setValueChangeListener(this); 195 dialog.show(getSupportFragmentManager(), "note_picker"); 196 } 197 198 return false; 199 } 200 201 @Override 202 public void onProgressUpdate(PitchDifference pitchDifference) { 203 TunerView tunerView = this.findViewById(R.id.pitch); 204 205 tunerView.setPitchDifference(pitchDifference); 206 tunerView.invalidate(); 207 } 208 209 @Override 210 public void onBackPressed() { 211 moveTaskToBack(true); 212 } 213 214 @Override 215 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 216 @NonNull int[] grantResults) { 217 if (requestCode == RECORD_AUDIO_PERMISSION) { 218 if (grantResults.length > 0) { 219 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 220 startRecording(); 221 } else { 222 AlertDialog alertDialog = new Builder(MainActivity.this).create(); 223 alertDialog.setTitle(R.string.permission_required); 224 alertDialog.setMessage(getString(R.string.microphone_permission_required)); 225 alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.ok), 226 (dialog, which) -> { 227 dialog.dismiss(); 228 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 229 finishAffinity(); 230 } else { 231 finish(); 232 } 233 }); 234 alertDialog.show(); 235 } 236 } 237 } 238 } 239 240 @Override 241 public void onItemSelected(MaterialSpinner view, int position, long id, Object item) { 242 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 243 MODE_PRIVATE); 244 245 SharedPreferences.Editor editor = preferences.edit(); 246 editor.putInt(CURRENT_TUNING, position); 247 editor.apply(); 248 249 tuningPosition = position; 250 251 isAutoModeEnabled = true; 252 referencePosition = 0; 253 254 recreate(); 255 } 256 257 @Override 258 public void onValueChange(NumberPicker picker, int oldValue, int newValue) { 259 String tag = String.valueOf(picker.getTag()); 260 if ("reference_pitch_picker".equalsIgnoreCase(tag)) { 261 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 262 MODE_PRIVATE); 263 264 SharedPreferences.Editor editor = preferences.edit(); 265 editor.putInt(REFERENCE_PITCH, newValue); 266 editor.apply(); 267 268 setReferencePitch(); 269 270 TunerView tunerView = this.findViewById(R.id.pitch); 271 tunerView.invalidate(); 272 } else if ("note_picker".equalsIgnoreCase(tag)) { 273 isAutoModeEnabled = newValue == 0; 274 275 referencePosition = newValue; 276 277 recreate(); 278 } 279 } 280 281 private void startRecording() { 282 FragmentManager fragmentManager = getSupportFragmentManager(); 283 ListenerFragment listenerFragment = (ListenerFragment) 284 fragmentManager.findFragmentByTag(TAG_LISTENER_FRAGMENT); 285 286 if (listenerFragment == null) { 287 listenerFragment = new ListenerFragment(); 288 fragmentManager 289 .beginTransaction() 290 .add(listenerFragment, TAG_LISTENER_FRAGMENT) 291 .commit(); 292 } 293 } 294 295 private void setTuning() { 296 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 297 MODE_PRIVATE); 298 tuningPosition = preferences.getInt(CURRENT_TUNING, 0); 299 300 int textColorDark = getResources().getColor(R.color.colorTextDark); 301 302 MaterialSpinner spinner = findViewById(R.id.tuning); 303 MaterialSpinnerAdapter<String> adapter = new MaterialSpinnerAdapter<>(this, 304 Arrays.asList(getResources().getStringArray(R.array.tunings))); 305 306 if (isDarkModeEnabled) { 307 spinner.setTextColor(textColorDark); 308 spinner.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark)); 309 spinner.setTextColor(textColorDark); 310 spinner.setArrowColor(textColorDark); 311 } 312 313 spinner.setAdapter(adapter); 314 spinner.setOnItemSelectedListener(this); 315 spinner.setSelectedIndex(tuningPosition); 316 } 317 318 private void enableTheme() { 319 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 320 MODE_PRIVATE); 321 isDarkModeEnabled = preferences.getBoolean(USE_DARK_MODE, true); 322 323 int mode = AppCompatDelegate.MODE_NIGHT_NO; 324 if (isDarkModeEnabled) { 325 mode = AppCompatDelegate.MODE_NIGHT_YES; 326 } 327 328 AppCompatDelegate.setDefaultNightMode(mode); 329 } 330 331 private void setReferencePitch() { 332 final SharedPreferences preferences = getSharedPreferences(PREFS_FILE, 333 MODE_PRIVATE); 334 referencePitch = preferences.getInt(REFERENCE_PITCH, 440); 335 } 336 337 private void requestRecordAudioPermission() { 338 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 339 RECORD_AUDIO_PERMISSION); 340 } 341 }