Skip to content

Commit 087d743

Browse files
committed
import settings (including data URL) using a QR code #43
the QR code has to contain text data in properties format, each line like "pref_name=the desired value" (without the quotation marks)
1 parent bd76124 commit 087d743

File tree

11 files changed

+222
-10
lines changed

11 files changed

+222
-10
lines changed

app/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ android {
1212
compileSdkVersion 31
1313

1414
compileOptions {
15+
// enable support for the new language APIs
16+
coreLibraryDesugaringEnabled true
17+
1518
sourceCompatibility JavaVersion.VERSION_11
1619
targetCompatibility JavaVersion.VERSION_11
1720
}
@@ -69,6 +72,10 @@ dependencies {
6972
implementation 'com.simplecityapps:recyclerview-fastscroll:2.0.1'
7073
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
7174

75+
implementation 'com.github.yuriy-budiyev:code-scanner:2.3.0'
76+
77+
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
78+
7279
// only necessary for testing, the app will use Android's implementation:
7380
testImplementation 'xmlpull:xmlpull:1.1.3.1'
7481
testImplementation 'org.ogce:xpp3:1.1.6'

app/src/main/AndroidManifest.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
package="org.zephyrsoft.sdbviewer">
55

66
<uses-permission android:name="android.permission.INTERNET" />
7+
<uses-permission android:name="android.permission.CAMERA" />
8+
9+
<uses-feature
10+
android:name="android.hardware.camera"
11+
android:required="false" />
712

813
<application
914
android:name=".SDBViewerApplication"
@@ -43,6 +48,15 @@
4348
android:name="android.support.PARENT_ACTIVITY"
4449
android:value="org.zephyrsoft.sdbviewer.SongListActivity" />
4550
</activity>
51+
<activity
52+
android:name=".QRScannerActivity"
53+
android:exported="false"
54+
android:label="@string/title_activity_import_settings"
55+
android:parentActivityName=".SongListActivity">
56+
<meta-data
57+
android:name="android.support.PARENT_ACTIVITY"
58+
android:value="org.zephyrsoft.sdbviewer.SongListActivity" />
59+
</activity>
4660
<activity
4761
android:name=".AboutActivity"
4862
android:exported="false"
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,67 @@
11
package org.zephyrsoft.sdbviewer;
22

3+
import android.content.Context;
4+
import android.preference.PreferenceManager;
5+
6+
import java.util.function.BiConsumer;
7+
import java.util.function.BiPredicate;
8+
import java.util.function.Predicate;
9+
310
public class Constants {
411
public static final String LOG_TAG = "sdbviewer";
512

613
public static final String FILE_LAST_UPDATED = "last_updated.txt";
714

815
static final String ARG_SONG = "song";
16+
17+
public enum AppPreference {
18+
SONGS_URL((s, c) -> c.getString(R.string.pref_songs_url).equals(s),
19+
s -> s != null && !s.isEmpty(),
20+
(s, c) -> PreferenceManager.getDefaultSharedPreferences(c).edit()
21+
.putString(c.getString(R.string.pref_songs_url), s).commit()),
22+
SONGS_RELOAD_INTERVAL((s, c) -> c.getString(R.string.pref_songs_reload_interval).equals(s), s -> {
23+
try {
24+
Integer.valueOf(s);
25+
return true;
26+
} catch (Exception e) {
27+
return false;
28+
}
29+
}, (s, c) -> PreferenceManager.getDefaultSharedPreferences(c).edit()
30+
.putString(c.getString(R.string.pref_songs_reload_interval), s).commit()),
31+
SHOW_TRANSLATION((s, c) -> c.getString(R.string.pref_show_translation).equals(s),
32+
s -> true,
33+
(s, c) -> PreferenceManager.getDefaultSharedPreferences(c).edit()
34+
.putBoolean(c.getString(R.string.pref_show_translation), Boolean.parseBoolean(s)).commit()),
35+
SHOW_CHORDS((s, c) -> c.getString(R.string.pref_show_chords).equals(s),
36+
s -> true,
37+
(s, c) -> PreferenceManager.getDefaultSharedPreferences(c).edit()
38+
.putBoolean(c.getString(R.string.pref_show_chords), Boolean.parseBoolean(s)).commit());
39+
40+
private final BiPredicate<String, Context> checkName;
41+
private final Predicate<String> checkValue;
42+
private final BiConsumer<String, Context> apply;
43+
44+
AppPreference(BiPredicate<String, Context> checkName, Predicate<String> checkValue, BiConsumer<String, Context> apply) {
45+
this.checkName = checkName;
46+
this.checkValue = checkValue;
47+
this.apply = apply;
48+
}
49+
50+
public boolean checkPossibleValue(String str) {
51+
return checkValue.test(str);
52+
}
53+
54+
public void applyValue(String str, Context context) {
55+
apply.accept(str, context);
56+
}
57+
58+
public static AppPreference get(String name, Context context) {
59+
for (AppPreference pref : AppPreference.values()) {
60+
if (pref.checkName.test(name, context)) {
61+
return pref;
62+
}
63+
}
64+
return null;
65+
}
66+
}
967
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.zephyrsoft.sdbviewer;
2+
3+
import android.app.Activity;
4+
import android.content.Context;
5+
import android.content.SharedPreferences;
6+
import android.os.Bundle;
7+
import android.preference.PreferenceActivity;
8+
import android.preference.PreferenceManager;
9+
import android.util.Log;
10+
import android.widget.Toast;
11+
12+
import androidx.appcompat.app.AppCompatActivity;
13+
14+
import com.budiyev.android.codescanner.CodeScanner;
15+
import com.budiyev.android.codescanner.CodeScannerView;
16+
import com.google.zxing.Result;
17+
18+
import java.util.stream.Stream;
19+
20+
public class QRScannerActivity extends AppCompatActivity {
21+
private CodeScanner mCodeScanner;
22+
23+
@Override
24+
protected void onCreate(Bundle savedInstanceState) {
25+
super.onCreate(savedInstanceState);
26+
setContentView(R.layout.activity_qrscanner);
27+
CodeScannerView scannerView = findViewById(R.id.scanner_view);
28+
mCodeScanner = new CodeScanner(this, scannerView);
29+
mCodeScanner.setDecodeCallback(this::checkResult);
30+
scannerView.setOnClickListener(view -> mCodeScanner.startPreview());
31+
}
32+
33+
private void checkResult(Result result) {
34+
if (result.getText()!=null) {
35+
Stream.of(result.getText().split("\n"))
36+
.filter(line -> !line.trim().isEmpty()
37+
&& !line.trim().startsWith("#")
38+
&& line.contains("="))
39+
.map(line -> line.split("=", 2))
40+
.forEach(entry -> applyPreferenceIfValid(entry[0], entry[1]));
41+
42+
runOnUiThread(() -> Toast.makeText(this, getText(R.string.settings_imported), Toast.LENGTH_LONG).show());
43+
finish();
44+
}
45+
}
46+
47+
private void applyPreferenceIfValid(String name, String value) {
48+
Constants.AppPreference pref = Constants.AppPreference.get(name, this);
49+
if (pref == null) {
50+
Log.i(Constants.LOG_TAG,"no preference found for: " + name);
51+
} else if (!pref.checkPossibleValue(value)) {
52+
Log.i(Constants.LOG_TAG,"preference "+name + ": value invalid: " + value);
53+
} else {
54+
pref.applyValue(value, this);
55+
Log.i(Constants.LOG_TAG,"preference "+name + ": value applied: " + value);
56+
}
57+
}
58+
59+
@Override
60+
protected void onResume() {
61+
super.onResume();
62+
mCodeScanner.startPreview();
63+
}
64+
65+
@Override
66+
protected void onPause() {
67+
mCodeScanner.releaseResources();
68+
super.onPause();
69+
}
70+
}

app/src/main/java/org/zephyrsoft/sdbviewer/SongListActivity.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
package org.zephyrsoft.sdbviewer;
22

3+
import android.Manifest;
34
import android.annotation.SuppressLint;
45
import android.content.Context;
56
import android.content.Intent;
67
import android.content.SharedPreferences;
7-
import android.graphics.Color;
8+
import android.content.pm.PackageManager;
89
import android.os.AsyncTask;
910
import android.os.Bundle;
1011
import android.os.Handler;
1112
import android.os.Parcelable;
1213
import android.preference.PreferenceManager;
13-
import androidx.annotation.ColorInt;
14-
import androidx.annotation.NonNull;
15-
import androidx.appcompat.app.AppCompatActivity;
16-
import androidx.recyclerview.widget.LinearLayoutManager;
17-
import androidx.recyclerview.widget.RecyclerView;
18-
import androidx.appcompat.widget.Toolbar;
1914
import android.util.Log;
2015
import android.view.LayoutInflater;
2116
import android.view.Menu;
@@ -26,6 +21,15 @@
2621
import android.widget.TextView;
2722
import android.widget.Toast;
2823

24+
import androidx.activity.result.ActivityResultLauncher;
25+
import androidx.activity.result.contract.ActivityResultContracts;
26+
import androidx.annotation.NonNull;
27+
import androidx.appcompat.app.AppCompatActivity;
28+
import androidx.appcompat.widget.Toolbar;
29+
import androidx.core.content.ContextCompat;
30+
import androidx.recyclerview.widget.LinearLayoutManager;
31+
import androidx.recyclerview.widget.RecyclerView;
32+
2933
import org.zephyrsoft.sdbviewer.fetch.FetchException;
3034
import org.zephyrsoft.sdbviewer.fetch.SDBFetcher;
3135
import org.zephyrsoft.sdbviewer.model.Song;
@@ -116,6 +120,7 @@ public boolean onCreateOptionsMenu(Menu menu) {
116120
public boolean onQueryTextSubmit(String query) {
117121
return handleSearchText(query);
118122
}
123+
119124
@Override
120125
public boolean onQueryTextChange(String newText) {
121126
return handleSearchText(newText);
@@ -153,6 +158,16 @@ public boolean onOptionsItemSelected(MenuItem item) {
153158
startActivity(intentSettings);
154159
return true;
155160

161+
case R.id.action_import_settings:
162+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ==
163+
PackageManager.PERMISSION_GRANTED) {
164+
Intent intentImportSettings = new Intent(this, QRScannerActivity.class);
165+
startActivity(intentImportSettings);
166+
} else {
167+
qrCodeScannerLauncher.launch(Manifest.permission.CAMERA);
168+
}
169+
return true;
170+
156171
case R.id.action_about:
157172
Intent intentAbout = new Intent(this, AboutActivity.class);
158173
startActivity(intentAbout);
@@ -166,6 +181,17 @@ public boolean onOptionsItemSelected(MenuItem item) {
166181
}
167182
}
168183

184+
private ActivityResultLauncher<String> qrCodeScannerLauncher =
185+
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
186+
if (isGranted) {
187+
Intent intentImportSettings = new Intent(this, QRScannerActivity.class);
188+
startActivity(intentImportSettings);
189+
} else {
190+
// TODO explain to the user that the QR scanner is unavailable because the user denied the permission
191+
}
192+
});
193+
194+
169195
private String getUrl() {
170196
SharedPreferences sharedPreferences =
171197
PreferenceManager.getDefaultSharedPreferences(this);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<FrameLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent">
7+
8+
<com.budiyev.android.codescanner.CodeScannerView
9+
android:id="@+id/scanner_view"
10+
android:layout_width="match_parent"
11+
android:layout_height="match_parent"
12+
app:autoFocusButtonColor="@android:color/white"
13+
app:autoFocusButtonVisible="true"
14+
app:flashButtonColor="@android:color/white"
15+
app:flashButtonVisible="true"
16+
app:frameColor="@android:color/white"
17+
app:frameCornersSize="50dp"
18+
app:frameCornersRadius="0dp"
19+
app:frameAspectRatioWidth="1"
20+
app:frameAspectRatioHeight="1"
21+
app:frameSize="0.75"
22+
app:frameThickness="2dp"
23+
app:frameVerticalBias="0.5"
24+
app:maskColor="#77000000"/>
25+
</FrameLayout>

app/src/main/res/menu/menu.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
android:title="@string/menu_settings"
2222
android:visible="true"/>
2323

24+
<item
25+
android:id="@+id/action_import_settings"
26+
app:showAsAction="never"
27+
android:enabled="true"
28+
android:title="@string/menu_import_settings"
29+
android:visible="true"/>
30+
2431
<item
2532
android:id="@+id/action_about"
2633
app:showAsAction="never"

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<string name="menu_refresh">Refresh</string>
66
<string name="menu_settings">Settings</string>
77
<string name="title_activity_settings">Settings</string>
8+
<string name="title_activity_import_settings">Import Settings</string>
9+
<string name="settings_imported">Settings imported.</string>
810

911
<string name="acra_title">Oops!</string>
1012
<string name="acra_text">Sorry, SDB Viewer just crashed. This should not happen. Please send an error report to the developer so the problem can be fixed!\n\nAll data contained in the report will be kept strictly confidential and only used to identify the problem.\n</string>
@@ -36,4 +38,5 @@
3638
</string>
3739
<string name="versionDescription">Version:</string>
3840
<string name="search">Search</string>
41+
<string name="menu_import_settings">Import settings from QR code</string>
3942
</resources>

app/src/main/res/xml/pref_display.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
android:defaultValue="true"
1010
android:key="@string/pref_show_chords"
1111
android:title="@string/pref_show_chords_desc"/>
12-
12+
<!-- don't forget to add new preferences to Constants.AppPreference -->
1313
</PreferenceScreen>

app/src/main/res/xml/pref_general.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
android:selectAllOnFocus="true"
2020
android:singleLine="true"
2121
android:title="@string/pref_songs_reload_interval_desc"/>
22-
22+
<!-- don't forget to add new preferences to Constants.AppPreference -->
2323
</PreferenceScreen>

0 commit comments

Comments
 (0)