Skip to content

Commit

Permalink
Merge pull request #2 from Jacusaurus/tossing_frags
Browse files Browse the repository at this point in the history
Tossing Frags: Migrate Navigation And Update Deprecated Code
Overhauls navigation to use the Navigation Framework (NavController), except when launching CameraActivity, and updates deprecated code.
Resolves #1
  • Loading branch information
Jacusaurus authored Jul 7, 2021
2 parents 7eb7418 + eae6164 commit 94f4e98
Show file tree
Hide file tree
Showing 16 changed files with 696 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Borderlands3WeaponDpsCalculator
An Android App for Borderlands 3 weapons DPS calculation
An Android App to calculate DPS and DPS sustained of Borderlands 2, 3, and Pre-Sequel weapons

A Weapon DPS Calculator I forked from https://github.com/RomainDeschenes/Borderlands3DpsCalculator

Expand Down
23 changes: 19 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId 'com.romaindeschenes.borderlands3dpscalculator'
minSdkVersion 19
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName '0.1.0'
Expand All @@ -21,14 +21,29 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-vision:15.0.0'
implementation 'org.jetbrains:annotations-java5:15.0'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
//implementation 'org.jetbrains:annotations-java5:15.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
implementation "org.greenrobot:eventbus:3.2.0"
implementation "androidx.core:core-ktx:1.5.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
def fragment_version = "1.3.5"

// Kotlin
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Java language implementation
implementation "androidx.fragment:fragment:$fragment_version"


}
repositories {
mavenCentral()
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@

@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityTest {
public class NavHostActivityTest {

@Rule
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class);
public ActivityTestRule<NavHostActivity> activityRule = new ActivityTestRule<>(NavHostActivity.class);

@Test
public void DPSIsDisplayedWhenWeaponIsEntered() {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.borderlandsdpscalculator.borderlands3dpscalculator.activities.CameraActivity" />
<activity android:name="com.borderlandsdpscalculator.borderlands3dpscalculator.activities.MainActivity">
<activity android:name="com.borderlandsdpscalculator.borderlands3dpscalculator.activities.NavHostActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,108 @@
package com.borderlandsdpscalculator.borderlands3dpscalculator.activities;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;

import com.borderlandsdpscalculator.borderlands3dpscalculator.R;
import com.borderlandsdpscalculator.borderlands3dpscalculator.models.Weapon;

public class MainActivity extends AppCompatActivity {
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;

public class MainActivity extends Fragment {
/* MainActivity extends Fragment, allowing it to be used more in a more modular way. MainActivity is not actually the default
* starting activity, but is the first activity/fragment that the user sees as it is the default view as defined in the navigation
graph "nav_graph.xml". */
private Weapon mWeapon = new Weapon(); // declares a Weapon model object whose variables will be passed to most other processes
// declares several text fields that will be bound to their corresponding views
EditText mEditTextDamage;
EditText mEditTextReloadTime;
EditText mEditTextFireRate;
EditText mEditTextMagazineSize;

// declares several text blocks that will be bound to their corresponding views
TextView mTextViewTimeToEmptyMagazine;
TextView mTextViewDamagePerSecond;
TextView mTextViewDamagePerMagazine;
TextView mTextViewTimeSpentShooting;
TextView mTextViewTimeSpentReloading;
TextView mTextViewDPSSustained;

Button mButton;

private Weapon mWeapon = new Weapon();
// declares several buttons that will be bound to their corresponding views
Button mAutoScanButton;
Button mButtonSaveWeapon;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mEditTextDamage = findViewById(R.id.editTextDamage);
mEditTextReloadTime = findViewById(R.id.editTextReloadTime);
mEditTextFireRate = findViewById(R.id.editTextFireRate);
mEditTextMagazineSize = findViewById(R.id.editTextMagazineSize);
// constructor that calls "MainActivity" constructor and gives main_activity.xml as a layout. Layout is subsequently inflated.
public MainActivity() {
super(R.layout.main_activity);
}

mTextViewDPSSustained = findViewById(R.id.textViewDamagePerSecondSustained);
mTextViewTimeToEmptyMagazine = findViewById(R.id.textViewEmptyMagazine);
mTextViewDamagePerSecond = findViewById(R.id.textViewDamagePerSecond);
mTextViewDamagePerMagazine = findViewById(R.id.textViewDamagePerMagazine);
mTextViewTimeSpentShooting = findViewById(R.id.textViewTimeSpentShooting);
mTextViewTimeSpentReloading = findViewById(R.id.textViewTimeSpentReloading);

mButton = findViewById(R.id.autoScanButton);
// https://stackoverflow.com/questions/62671106/onactivityresult-method-is-deprecated-what-is-the-alternative
// defines actions to be executed upon completion of an activity (cameraActivity)
ActivityResultLauncher<Intent> cameraActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) { // code that executes when Activity completes
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData(); //gets data from result and saves it as an Intent
if (data != null) {
buildWeapon(data); //build weapon if data was returned from activity
}

}
}
});

initListeners();
// overrides onViewCreated(). Executes after view is created, and therefore, after onCreate() and onCreateView().
// getView() only works after onCreateView(), which is why the overridden method is onViewCreated()
// see https://stackoverflow.com/a/6496000/7136798 for more details
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
// method accepts a View, which is the View created by onCreateView(), and a Bundle, which is the bundle
// passed into onCreateView().
// the following statements bind View elements (from layout file) to their corresponding class variables
mEditTextDamage = getView().findViewById(R.id.editTextDamage);
mEditTextReloadTime = getView().findViewById(R.id.editTextReloadTime);
mEditTextFireRate = getView().findViewById(R.id.editTextFireRate);
mEditTextMagazineSize = getView().findViewById(R.id.editTextMagazineSize);

mTextViewDPSSustained = getView().findViewById(R.id.textViewDamagePerSecondSustained);
mTextViewTimeToEmptyMagazine = getView().findViewById(R.id.textViewEmptyMagazine);
mTextViewDamagePerSecond = getView().findViewById(R.id.textViewDamagePerSecond);
mTextViewDamagePerMagazine = getView().findViewById(R.id.textViewDamagePerMagazine);
mTextViewTimeSpentShooting = getView().findViewById(R.id.textViewTimeSpentShooting);
mTextViewTimeSpentReloading = getView().findViewById(R.id.textViewTimeSpentReloading);

mAutoScanButton = getView().findViewById(R.id.autoScanButton);
mButtonSaveWeapon = getView().findViewById(R.id.saveWeaponButton);

initListeners(); //calls method to initialize listeners for bound objects
}


// function to initialize listeners
protected void initListeners() {
TextWatcher textWatcher = new TextWatcher() {
TextWatcher textWatcher = new TextWatcher() { // TextWatcher objects are called when an "Editable" object (that it's attached to) is changed
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

Expand All @@ -68,60 +114,112 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
}

@Override
public void afterTextChanged(Editable s) {
public void afterTextChanged(Editable s) { // I'm only concerned with the behavior that occurs *after* text is changed
updateWeapon();
updateTextViews();
}
};

// the following statements attach the previously instantiated textWatcher to several "Editable" objects through the use of a text-change listener
mEditTextDamage.addTextChangedListener(textWatcher);
mEditTextReloadTime.addTextChangedListener(textWatcher);
mEditTextFireRate.addTextChangedListener(textWatcher);
mEditTextMagazineSize.addTextChangedListener(textWatcher);

mButton.setOnClickListener(new View.OnClickListener() {
// when clicked, the autoscan will launch a CameraActivity
mAutoScanButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), CameraActivity.class);
startActivityForResult(intent, 1);
// creates intent to launch CameraActivity. getActivity() returns the FragmentActivity (or AppCompatActivity) that the current fragment is associated with (NavHostActivity in this case)
// getApplicationContext() returns the context of the global Application object of the current process (in this case returns context of NavHostActivity)
Intent intent = new Intent(getActivity().getApplicationContext(), CameraActivity.class);
cameraActivityResultLauncher.launch(intent); //activity is launched and program flow control is passed to cameraActivityResultLauncher, which handles post-activity logic
}
});

// when clicked, the button to save your weapon launches an event that switches the active fragment
mButtonSaveWeapon.setOnClickListener(view -> {
//((NavHostActivity) getActivity()).switchFragment(SaveWeapon.class);
//EventBus.getDefault().post(new NavHostActivity.SwitchFragEvent(SaveWeapon.class));
//Navigation.findNavController(view).navigate(R.id.action_mainActivityFrag_to_saveWeaponFrag);
//launches activity by posting an event using EventBus and passing control to the subscribing method
EventBus.getDefault().post(new NavHostActivity.SwitchFragEvent(R.id.action_mainActivityFrag_to_saveWeaponFrag, view));
/* EventBus is used because it contains a simple framework that can be used to pass data from the calling activity (or fragment) to the child fragment.
* It's also super re-usable and allows me to write a few less lines of code (theoretically) per fragment switch.
See https://stackoverflow.com/questions/13216916/how-to-replace-the-activitys-fragment-from-the-fragment-itself#comment56148125_13221546 for another reason why you should use EventBus. */
});
}

//onStart() registers the EventBus listener so that it picks up any posts that occur. This subscription is valid for the duration of the lifecycle of the calling class (MainActivity).
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == 1) {
if(resultCode == Activity.RESULT_OK){
Weapon resultWeapon = (Weapon)data.getSerializableExtra("weapon");
if (resultWeapon != null) {
mEditTextDamage.setText(String.valueOf(resultWeapon.getDamage()));
mEditTextReloadTime.setText(String.valueOf(resultWeapon.getReloadTime()));
mEditTextFireRate.setText(String.valueOf(resultWeapon.getFireRate()));
mEditTextMagazineSize.setText(String.valueOf(resultWeapon.getMagazineSize()));
}
}
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

//onsStop() unregisters the EventBus listener when the class'/activity's lifecycle ends. Because MainActivity is the "start destination" (defined in nav_graph.xml), the lifecycle should extend to the lifetime of the application.
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}

//a "subscriber" method that handles behavior when a SwitchFragEvent is posted. Reads as unused because it is executed asynchronously, once an event is posted.
@Subscribe
public void switchFragment(NavHostActivity.SwitchFragEvent event) {
int resId = event.resId; //sets resID according to the event object's resId value
View view = event.view; // sets view object according to the event object's View
Log.d("SwitchFragEvent", "success!");
//creates bundle containing weapon info to pass to the newly displayed fragment
Bundle bundle = new Bundle();
bundle.putString("weapon", mWeapon.toString());
bundle.putInt("damage", mWeapon.getDamage());
bundle.putFloat("reload_time", mWeapon.getReloadTime());
bundle.putFloat("fire_rate", mWeapon.getFireRate());
bundle.putInt("magazine_size", mWeapon.getMagazineSize());

//bundle.putString("current_weapon", (getSupportFragmentManager().findFragmentByTag("main_activity")).getCurrentWeapon);
//navigates to the given resId (or with the action defined by resId). findNavController returns the NavController given by the view of the onClick listener (or other caller that posts a compatible event).
Navigation.findNavController(view).navigate(resId);
}


//sets displayed text according to data returned to it by an external activity/fragment
private void buildWeapon(Intent data) {
Weapon resultWeapon = (Weapon) data.getSerializableExtra("weapon"); //gets data from Intent and creates a Weapon object with it
//sets displayed values (in the editable text fields) according to the values of resultWeapon
if (resultWeapon != null) { //check to ensure that there was sufficient data passed to construct a Weapon (otherwise resultWeapon is null)
mEditTextDamage.setText(String.valueOf(resultWeapon.getDamage()));
mEditTextReloadTime.setText(String.valueOf(resultWeapon.getReloadTime()));
mEditTextFireRate.setText(String.valueOf(resultWeapon.getFireRate()));
mEditTextMagazineSize.setText(String.valueOf(resultWeapon.getMagazineSize()));
}
}

//updates mWeapon values according to user input in text fields
private void updateWeapon() {
if (!mEditTextDamage.getText().toString().equals("") && !mEditTextReloadTime.getText().toString().equals("") && !mEditTextFireRate.getText().toString().equals("") && !mEditTextMagazineSize.getText().toString().equals("")) {
//checks to ensure that none of the text fields were changed to be blank (e.g. user hits backspace) and assigns these values to mWeapon accordingly
if (!mEditTextDamage.getText().toString().equals("") && !mEditTextReloadTime.getText().toString().equals("")
&& !mEditTextFireRate.getText().toString().equals("") && !mEditTextMagazineSize.getText().toString().equals("")) {
int damage = Integer.parseInt(mEditTextDamage.getText().toString());
float reloadTime = Float.parseFloat(mEditTextReloadTime.getText().toString());
float fireRate = Float.parseFloat(mEditTextFireRate.getText().toString());
int magazineSize = Integer.parseInt(mEditTextMagazineSize.getText().toString());

// Get all values
//sets mWeapon variable values according to local variables, which are defined above
mWeapon.setDamage(damage);
mWeapon.setReloadTime(reloadTime);
mWeapon.setFireRate(fireRate);
mWeapon.setMagazineSize(magazineSize);
}
}

//updates text blocks according to the values of mWeapon
protected void updateTextViews() {
//I think I calculated the following two instead of getting them from the Weapon class because it didn't print them nice when I tried it the other way, but I can't be sure ¯\_(ツ)_/¯
double dpsSustained = Math.round(mWeapon.getDPSSustained() * 100.0) / 100.0;
double dps = Math.round(mWeapon.getDamagePerSecond() * 100.0) / 100.0;

//updates values of text blocks
mTextViewDPSSustained.setText(String.valueOf(dpsSustained));
mTextViewDamagePerSecond.setText(String.valueOf(dps));
mTextViewTimeToEmptyMagazine.setText(String.valueOf(mWeapon.getTimeToEmptyMagazine()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.borderlandsdpscalculator.borderlands3dpscalculator.activities;

import android.view.View;

import androidx.annotation.IdRes;
import androidx.appcompat.app.AppCompatActivity;

import com.borderlandsdpscalculator.borderlands3dpscalculator.R;

//FragmentActivity is the base class for AppCompatActivity, so we can use AppCompatActivity to contain and display a fragment
public class NavHostActivity extends AppCompatActivity {

public NavHostActivity() { //constructor instantiates and inflates layout with a Fragment Container View, which is used to hold and display different Fragments
super(R.layout.nav_host_container);
}

public static class SwitchFragEvent { //acts as an event that is accessible from anywhere (provided a NavHostActivity object has already been instantiated)
//following variables are final, as they are not changed after being set during instantiation, and public, because the subscriber needs to be able to retrieve their values to perform its function
public final int resId;
public final View view;

public SwitchFragEvent(@IdRes int resId, View view) { //constructor that takes two parameters and sets instance variables with them. Necessary to pass data from caller to subscriber outside of caller scope.
this.resId = resId;
this.view = view;
}
}

}
Loading

0 comments on commit 94f4e98

Please sign in to comment.