Skip to content

Commit e8a9ae0

Browse files
committed
Add support for using QR code to add new conference
Support using a QR code directly from the app instead of going through the OS. This makes it work with URLs that are not pre-registered, and is a more intuitive way. To do this, make the add conference be an activity which can take either a pasted URL or a QR code.
1 parent ada6e7f commit e8a9ae0

4 files changed

Lines changed: 272 additions & 13 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<activity android:name=".CheckinStatsActivity"/>
1717
<activity android:name=".AttendeeCheckinActivity" />
1818
<activity android:name=".ListConferencesActivity" />
19+
<activity android:name=".AddConferenceActivity" />
1920
<activity android:name=".GlobalSettingsActivity" android:label="Preferences" />
2021

2122
<activity
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package eu.postgresql.android.conferencescanner;
2+
3+
import android.Manifest;
4+
import android.content.Intent;
5+
import android.content.pm.PackageManager;
6+
import android.os.Bundle;
7+
import android.text.Editable;
8+
import android.text.TextWatcher;
9+
import android.util.Log;
10+
import android.view.MenuItem;
11+
import android.view.View;
12+
import android.widget.Button;
13+
import android.widget.EditText;
14+
15+
import androidx.annotation.NonNull;
16+
import androidx.appcompat.app.AppCompatActivity;
17+
import androidx.camera.core.CameraSelector;
18+
import androidx.camera.core.ImageAnalysis;
19+
import androidx.camera.core.Preview;
20+
import androidx.camera.lifecycle.ProcessCameraProvider;
21+
import androidx.camera.view.PreviewView;
22+
import androidx.core.app.ActivityCompat;
23+
import androidx.core.content.ContextCompat;
24+
25+
import com.google.common.util.concurrent.ListenableFuture;
26+
27+
import java.util.concurrent.ExecutionException;
28+
29+
30+
public class AddConferenceActivity extends AppCompatActivity
31+
implements QRAnalyzer.QRNotificationReceiver {
32+
33+
private static final int PERMSSION_REQUEST_CAMERA = 17;
34+
35+
private PreviewView viewfinder;
36+
private Button scanbutton;
37+
private Button addurlbutton;
38+
private EditText addurl;
39+
40+
@Override
41+
protected void onCreate(Bundle savedInstanceState) {
42+
super.onCreate(savedInstanceState);
43+
44+
setContentView(R.layout.activity_add_conference);
45+
46+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
47+
getSupportActionBar().setDisplayShowHomeEnabled(true);
48+
49+
viewfinder = findViewById(R.id.view_finder);
50+
scanbutton = findViewById(R.id.scanbutton);
51+
addurlbutton = findViewById(R.id.addurlbutton);
52+
addurl = findViewById(R.id.addurl);
53+
54+
viewfinder.setVisibility(View.INVISIBLE);
55+
scanbutton.setOnClickListener(view -> {
56+
if (ContextCompat.checkSelfPermission(AddConferenceActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
57+
ActivityCompat.requestPermissions(AddConferenceActivity.this,
58+
new String[]{Manifest.permission.CAMERA},
59+
PERMSSION_REQUEST_CAMERA);
60+
return;
61+
}
62+
63+
StartCamera();
64+
});
65+
66+
addurlbutton.setOnClickListener(view -> {
67+
ReturnWithUrl(addurl.getText().toString());
68+
});
69+
addurl.addTextChangedListener(new TextWatcher() {
70+
@Override
71+
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
72+
}
73+
74+
@Override
75+
public void onTextChanged(CharSequence s, int start, int before, int count) {
76+
}
77+
78+
@Override
79+
public void afterTextChanged(Editable s) {
80+
addurlbutton.setEnabled(MainActivity.GetConferenceUrlMatcher(addurl.getText().toString()).matches());
81+
}
82+
});
83+
}
84+
85+
@Override
86+
public boolean onOptionsItemSelected(MenuItem item) {
87+
if (item.getItemId() == android.R.id.home) {
88+
StopCamera();
89+
finish();
90+
}
91+
92+
return super.onOptionsItemSelected(item);
93+
}
94+
95+
@Override
96+
public void onRequestPermissionsResult(int requestCode,
97+
@NonNull String[] permissions, @NonNull int[] grantResults) {
98+
if (requestCode == PERMSSION_REQUEST_CAMERA) {
99+
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
100+
/* Permission granted for camera, so start up the scanning */
101+
StartCamera();
102+
}
103+
}
104+
}
105+
106+
private void StopCamera() {
107+
try {
108+
ProcessCameraProvider.getInstance(this).get().unbindAll();
109+
} catch (ExecutionException e) {
110+
Log.e("conferencescanner", "Unable to unbind");
111+
} catch (InterruptedException e) {
112+
Log.e("conferencescanner", "Interrupted in unbind");
113+
}
114+
}
115+
116+
private void StartCamera() {
117+
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
118+
cameraProviderFuture.addListener(new Runnable() {
119+
@Override
120+
public void run() {
121+
ProcessCameraProvider cameraProvider = null;
122+
try {
123+
cameraProvider = cameraProviderFuture.get();
124+
} catch (ExecutionException e) {
125+
return;
126+
} catch (InterruptedException e) {
127+
return;
128+
}
129+
130+
scanbutton.setEnabled(false);
131+
132+
Preview preview = new Preview.Builder().build();
133+
preview.setSurfaceProvider(viewfinder.getSurfaceProvider());
134+
135+
viewfinder.setVisibility(View.VISIBLE);
136+
137+
ImageAnalysis analysis = new ImageAnalysis.Builder()
138+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
139+
.build();
140+
analysis.setAnalyzer(ContextCompat.getMainExecutor(AddConferenceActivity.this), new QRAnalyzer(AddConferenceActivity.this));
141+
142+
CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
143+
cameraProvider.unbindAll();
144+
cameraProvider.bindToLifecycle(AddConferenceActivity.this, cameraSelector, preview, analysis);
145+
}
146+
},
147+
ContextCompat.getMainExecutor(this)
148+
);
149+
}
150+
151+
@Override
152+
public void OnQRCodeFound(String qrstring) {
153+
ReturnWithUrl(qrstring);
154+
}
155+
156+
private void ReturnWithUrl(String url) {
157+
Intent i = new Intent();
158+
i.putExtra("url", url);
159+
setResult(RESULT_OK, i);
160+
161+
finish();
162+
}
163+
}

app/src/main/java/eu/postgresql/android/conferencescanner/MainActivity.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public class MainActivity extends AppCompatActivity
8686

8787
private static final int INTENT_RESULT_LIST_UPDATED = 1;
8888
private static final int INTENT_RESULT_CHECKED_IN = 2;
89+
private static final int INTENT_RESULT_ADD_CONFERENCE = 3;
8990

9091
public static final int RESULT_ERROR = -2;
9192
private Menu optionsMenu;
@@ -517,17 +518,7 @@ public boolean onNavigationItemSelected(MenuItem item) {
517518
int id = item.getItemId();
518519

519520
if (id == R.id.itemAdd) {
520-
final EditText input = new EditText(this);
521-
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
522-
input.setHint("https://test.com/events/test/checkin/abc123def456/");
523-
524-
new MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_MaterialComponents_MaterialAlertDialog_FullWidthButtons)
525-
.setTitle("Enter URL")
526-
.setMessage("Paste the full URL for scanning application (this will be an URL that contains a long random set of characters at the end).\n\nNote that in most cases you can also click the link in the email or on the website where you received it, and the conference will automatically be added.")
527-
.setView(input)
528-
.setNegativeButton("Cancel", null)
529-
.setPositiveButton("Add", (dialogInterface, i) -> AddNewConference(input.getText().toString()))
530-
.show();
521+
startActivityForResult(new Intent(this, AddConferenceActivity.class), INTENT_RESULT_ADD_CONFERENCE);
531522
} else if (id == R.id.itemManage) {
532523
startActivityForResult(new Intent(this, ListConferencesActivity.class), INTENT_RESULT_LIST_UPDATED);
533524
} else if (id >= MENU_FIRST_CONFERENCE && id < MENU_FIRST_CONFERENCE + conferences.size()) {
@@ -591,14 +582,22 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
591582
/* Canceled */
592583
pauseDetection = false;
593584
}
585+
} else if (requestCode == INTENT_RESULT_ADD_CONFERENCE) {
586+
if (resultCode == RESULT_OK) {
587+
AddNewConference(data.getStringExtra("url"));
588+
}
594589
}
595590
}
596591

597-
private String _clean_conference_url(String url) {
592+
private static String _clean_conference_url(String url) {
598593
return url.replaceAll("[/#]+$", "");
599594
}
600595

601-
private final Pattern urlpattern = Pattern.compile("^https?://[^/]+/events/[^/]+/(checkin|scanning)/[a-z0-9]+(/f([A-Za-z0-9]+))?$");
596+
public static final Pattern urlpattern = Pattern.compile("^https?://[^/]+/events/[^/]+/(checkin|scanning)/[a-z0-9]+(/f([A-Za-z0-9]+))?$");
597+
598+
public static Matcher GetConferenceUrlMatcher(String url) {
599+
return urlpattern.matcher(_clean_conference_url(url));
600+
}
602601

603602
private void AddNewConference(String url) {
604603
String cleanurl = _clean_conference_url(url);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/add_conference_wrap"
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent"
8+
android:fitsSystemWindows="true"
9+
>
10+
11+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
12+
xmlns:app="http://schemas.android.com/apk/res-auto"
13+
xmlns:tools="http://schemas.android.com/tools"
14+
android:layout_width="match_parent"
15+
android:layout_height="match_parent"
16+
tools:context=".AddConferenceActivity">
17+
18+
<TextView
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"
21+
android:id="@+id/txt_intro"
22+
app:layout_constraintLeft_toLeftOf="parent"
23+
app:layout_constraintRight_toRightOf="parent"
24+
app:layout_constraintTop_toTopOf="parent"
25+
android:textSize="18sp"
26+
android:layout_marginBottom="8dp"
27+
android:layout_marginLeft="8dp"
28+
android:layout_marginRight="8dp"
29+
android:layout_marginTop="8dp"
30+
android:text="You can use any of the below methods to add a conference"
31+
/>
32+
33+
<com.google.android.material.textfield.TextInputEditText
34+
android:id="@+id/addurl"
35+
android:layout_width="match_parent"
36+
android:layout_height="wrap_content"
37+
android:hint="Paste the URL to add"
38+
android:layout_marginTop="20dp"
39+
android:layout_marginBottom="8dp"
40+
android:layout_marginLeft="8dp"
41+
android:layout_marginRight="8dp"
42+
android:singleLine="true"
43+
app:layout_constraintTop_toBottomOf="@id/txt_intro"
44+
app:layout_constraintLeft_toLeftOf="parent"
45+
app:layout_constraintRight_toRightOf="parent"
46+
android:textSize="18sp"
47+
/>
48+
49+
<com.google.android.material.button.MaterialButton
50+
android:id="@+id/addurlbutton"
51+
android:layout_width="match_parent"
52+
android:layout_height="64dp"
53+
android:text="Add by URL"
54+
android:layout_marginTop="8dp"
55+
android:layout_marginBottom="8dp"
56+
android:layout_marginLeft="8dp"
57+
android:layout_marginRight="8dp"
58+
android:enabled="false"
59+
app:layout_constraintTop_toBottomOf="@id/addurl"
60+
app:layout_constraintLeft_toLeftOf="parent"
61+
app:layout_constraintRight_toRightOf="parent"
62+
android:textSize="18sp"
63+
/>
64+
65+
<com.google.android.material.button.MaterialButton
66+
android:id="@+id/scanbutton"
67+
android:layout_width="match_parent"
68+
android:layout_height="64dp"
69+
android:text="Scan QR code"
70+
android:layout_marginTop="20dp"
71+
android:layout_marginBottom="8dp"
72+
android:layout_marginLeft="8dp"
73+
android:layout_marginRight="8dp"
74+
app:layout_constraintTop_toBottomOf="@id/addurlbutton"
75+
app:layout_constraintLeft_toLeftOf="parent"
76+
app:layout_constraintRight_toRightOf="parent"
77+
android:textSize="18sp"
78+
/>
79+
80+
<androidx.camera.view.PreviewView
81+
android:id="@+id/view_finder"
82+
android:layout_marginLeft="16dp"
83+
android:layout_marginRight="16dp"
84+
android:layout_width="0dp"
85+
android:layout_height="0dp"
86+
87+
android:layout_marginTop="40dp"
88+
89+
app:layout_constraintDimensionRatio="1:1"
90+
app:layout_constraintTop_toBottomOf="@id/scanbutton"
91+
app:layout_constraintLeft_toLeftOf="parent"
92+
app:layout_constraintRight_toRightOf="parent"
93+
/>
94+
95+
</androidx.constraintlayout.widget.ConstraintLayout>
96+
</androidx.drawerlayout.widget.DrawerLayout>

0 commit comments

Comments
 (0)