diff --git a/.img/screenshot.png b/.img/screenshot.png new file mode 100755 index 0000000..798afeb Binary files /dev/null and b/.img/screenshot.png differ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..b2d3584 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'org.clubelec.clubelecemailkiosk' + compileSdk 33 + + defaultConfig { + applicationId "org.clubelec.clubelecemailkiosk" + minSdk 21 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cf001c2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/clubelec/clubelecemailkiosk/activity/MainActivity.java b/app/src/main/java/org/clubelec/clubelecemailkiosk/activity/MainActivity.java new file mode 100644 index 0000000..b98492b --- /dev/null +++ b/app/src/main/java/org/clubelec/clubelecemailkiosk/activity/MainActivity.java @@ -0,0 +1,361 @@ +package org.clubelec.clubelecemailkiosk.activity; + +import android.app.Activity; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.text.InputFilter; +import android.text.InputType; +import android.text.TextUtils; +import android.util.Patterns; +import android.view.View; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.material.elevation.SurfaceColors; + +import org.clubelec.clubelecemailkiosk.R; +import org.clubelec.clubelecemailkiosk.helper.DatabaseHelper; +import org.clubelec.clubelecemailkiosk.helper.SharedPrefManager; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity { + + private static final String PREF_NOT_FIRST_LAUNCH = "not_first_launch"; + private static final String PREF_PASSWORD = "password"; + private static final int REQUEST_CODE_EXPORT_EMAILS = 2; + private static final int REQUIRED_TAP_COUNT = 10; + private static final long MAX_TAP_DELAY = 3000; + private DatabaseHelper databaseHelper; + private EditText editTextEmailAddress; + private CheckBox checkBox; + private Button button; + private int tapCount = 0; + private int passwordMaxLength = 6; + private Handler handler; + private Runnable tapResetRunnable; + private SharedPrefManager sharedPrefManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getWindow().setStatusBarColor(SurfaceColors.SURFACE_2.getColor(this)); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + + getWindow().getDecorView() + .setOnSystemUiVisibilityChangeListener(visibility -> { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + hideSystemUI(getWindow()); + } + }); + + sharedPrefManager = SharedPrefManager.getInstance(getApplicationContext()); + boolean isNotFirstLaunch = sharedPrefManager.getBool(PREF_NOT_FIRST_LAUNCH); + + if (!isNotFirstLaunch) { + showFirstLaunchPasswordInputDialog(); + hideSystemUI(getWindow()); + } + + databaseHelper = new DatabaseHelper(this); + + editTextEmailAddress = findViewById(R.id.editTextTextEmailAddress); + editTextEmailAddress.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + hideKeyboard(v); + } + }); + checkBox = findViewById(R.id.checkBox); + button = findViewById(R.id.button); + + button.setOnClickListener(v -> onButtonClick()); + + ImageView logoImageView = findViewById(R.id.activity_main_clubelec_logo); + logoImageView.setOnClickListener(v -> onLogoClick()); + + handler = new Handler(); + tapResetRunnable = () -> tapCount = 0; + } + + private void showFirstLaunchPasswordInputDialog() { + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + InputFilter[] filters = new InputFilter[]{new InputFilter.LengthFilter(passwordMaxLength)}; + input.setFilters(filters); + new AlertDialog.Builder(this) + .setTitle(R.string.first_launch_password) + .setMessage(R.string.first_launch_password_desc) + .setView(input) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + String password = input.getText().toString(); + if (!TextUtils.isEmpty(password) && password.length() == passwordMaxLength) { + sharedPrefManager.saveBool(PREF_NOT_FIRST_LAUNCH, true); + sharedPrefManager.saveString(PREF_PASSWORD, password); + } else { + showFirstLaunchPasswordInputDialog(); + } + }) + .setCancelable(false) + .show(); + } + + private void onButtonClick() { + String email = editTextEmailAddress.getText().toString(); + boolean isCheckBoxChecked = checkBox.isChecked(); + + if (TextUtils.isEmpty(email)) { + return; + } + + if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) { + showErrorDialog(getString(R.string.invalid_email_format)); + } else { + if (!isCheckBoxChecked) { + showErrorDialog(getString(R.string.checkbox_unchecked)); + } else { + showConfirmationDialog(getString(R.string.is_email_correct) + email); + } + } + } + + private void showConfirmationDialog(String message) { + new AlertDialog.Builder(this) + .setTitle(R.string.confirmation_dialog) + .setMessage(message) + .setPositiveButton(android.R.string.yes, (dialog, which) -> { + String email = editTextEmailAddress.getText().toString(); + saveEmailToDatabase(email); + editTextEmailAddress = findViewById(R.id.editTextTextEmailAddress); + checkBox = findViewById(R.id.checkBox); + editTextEmailAddress.setText(""); + checkBox.setChecked(false); + Toast.makeText(MainActivity.this, getString(R.string.email_saved), Toast.LENGTH_SHORT).show(); + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } + + private void saveEmailToDatabase(String email) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(DatabaseHelper.COLUMN_EMAIL, email); + db.insert(DatabaseHelper.TABLE_EMAILS, null, values); + } + + + private void showErrorDialog(String message) { + new AlertDialog.Builder(this) + .setTitle(R.string.error) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + private void onLogoClick() { + tapCount++; + if (tapCount == REQUIRED_TAP_COUNT) { + handler.removeCallbacks(tapResetRunnable); + handler.postDelayed(tapResetRunnable, MAX_TAP_DELAY); + showPasswordDialog(); + } else if (tapCount > REQUIRED_TAP_COUNT) { + handler.removeCallbacks(tapResetRunnable); + tapCount = 1; + } else { + handler.removeCallbacks(tapResetRunnable); + handler.postDelayed(tapResetRunnable, MAX_TAP_DELAY); + } + } + + private void showPasswordDialog() { + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + InputFilter[] filters = new InputFilter[]{new InputFilter.LengthFilter(passwordMaxLength)}; + input.setFilters(filters); + + new AlertDialog.Builder(this) + .setTitle(R.string.enter_password) + .setView(input) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + String enteredPassword = input.getText().toString(); + if (enteredPassword.length() != passwordMaxLength) { + showErrorDialog(getString(R.string.enter_password_incorrect_format)); + } else { + String storedPassword = sharedPrefManager.getString(PREF_PASSWORD); + + if (enteredPassword.equals(storedPassword)) { + showActionsDialog(); + } else { + showErrorDialog(getString(R.string.enter_password_incorrect)); + } + } + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void showActionsDialog() { + String[] actions = {getString(R.string.show_emails), getString(R.string.export_emails), getString(R.string.clear_database)}; + new AlertDialog.Builder(this) + .setTitle(R.string.select_action) + .setItems(actions, (dialog, which) -> { + switch (which) { + case 0: + showEmails(); + break; + case 1: + openExportActivity(); + break; + case 2: + clearDatabase(); + break; + } + }) + .show(); + } + + private void showEmails() { + List emails = getEmailsFromDatabase(); + StringBuilder builder = new StringBuilder(); + for (String email : emails) { + builder.append(email).append("\n"); + } + + new AlertDialog.Builder(this) + .setTitle(R.string.saved_emails) + .setMessage(builder.toString()) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + private void clearDatabase() { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.delete(DatabaseHelper.TABLE_EMAILS, null, null); + Toast.makeText(this, getString(R.string.database_cleared), Toast.LENGTH_SHORT).show(); + } + + private void openExportActivity() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TITLE, "emails.txt"); + startActivityForResult(intent, REQUEST_CODE_EXPORT_EMAILS); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_EXPORT_EMAILS && resultCode == Activity.RESULT_OK) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + exportEmails(uri); + } + } + } + + private void exportEmails(Uri uri) { + List emailsList = getEmailsFromDatabase(); + + if (emailsList.isEmpty()) { + Toast.makeText(this, getString(R.string.no_emails_found), Toast.LENGTH_SHORT).show(); + return; + } + + try { + OutputStream outputStream = getContentResolver().openOutputStream(uri); + if (outputStream != null) { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + for (String email : emailsList) { + writer.write(email); + writer.newLine(); + } + writer.close(); + Toast.makeText(this, getString(R.string.emails_exported), Toast.LENGTH_SHORT).show(); + } + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(this, getString(R.string.export_failed), Toast.LENGTH_SHORT).show(); + } + } + + private List getEmailsFromDatabase() { + List emails = new ArrayList<>(); + + SQLiteDatabase db = databaseHelper.getReadableDatabase(); + String[] projection = {DatabaseHelper.COLUMN_EMAIL}; + Cursor cursor = db.query(DatabaseHelper.TABLE_EMAILS, projection, null, null, null, null, null); + + if (cursor != null) { + while (cursor.moveToNext()) { + String email = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseHelper.COLUMN_EMAIL)); + emails.add(email); + } + cursor.close(); + } + + return emails; + } + + public void hideKeyboard(View view) { + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + hideSystemUI(getWindow()); + } + + @Override + public void onResume() { + super.onResume(); + hideSystemUI(getWindow()); + } + + @Override + public void onBackPressed() { + + } + + public void hideSystemUI(Window window) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.getInsetsController().hide(WindowInsets.Type.systemBars()); + } else { + View decorView = window.getDecorView(); + int uiVisibility = decorView.getSystemUiVisibility(); + uiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; + uiVisibility |= View.SYSTEM_UI_FLAG_FULLSCREEN; + uiVisibility |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + uiVisibility |= View.SYSTEM_UI_FLAG_IMMERSIVE; + uiVisibility |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + decorView.setSystemUiVisibility(uiVisibility); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/DatabaseHelper.java b/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/DatabaseHelper.java new file mode 100644 index 0000000..234e97d --- /dev/null +++ b/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/DatabaseHelper.java @@ -0,0 +1,35 @@ +package org.clubelec.clubelecemailkiosk.helper; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class DatabaseHelper extends SQLiteOpenHelper { + + public static final String TABLE_EMAILS = "emails"; + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_EMAIL = "email"; + private static final String DATABASE_NAME = "email.db"; + private static final int DATABASE_VERSION = 1; + private static final String CREATE_TABLE_EMAILS = + "CREATE TABLE " + TABLE_EMAILS + "(" + + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_EMAIL + " TEXT NOT NULL" + + ")"; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_EMAILS); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_EMAILS); + onCreate(db); + } +} + diff --git a/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/SharedPrefManager.java b/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/SharedPrefManager.java new file mode 100644 index 0000000..2525101 --- /dev/null +++ b/app/src/main/java/org/clubelec/clubelecemailkiosk/helper/SharedPrefManager.java @@ -0,0 +1,133 @@ +package org.clubelec.clubelecemailkiosk.helper; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SharedPrefManager { + private static final String SHARED_PREF_NAME = "travelogue_sharepref"; + private static SharedPrefManager mInstance; + private final Context mCtx; + + private SharedPrefManager(Context mCtx) { + this.mCtx = mCtx; + } + + public static synchronized SharedPrefManager getInstance(Context mCtx) { + if (mInstance == null) { + mInstance = new SharedPrefManager(mCtx); + } + return mInstance; + } + + public void saveString(String key, String value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(key, value); + editor.apply(); + } + + public void saveBool(String key, boolean value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(key, value); + editor.apply(); + } + + public void saveInt(String key, int value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putInt(key, value); + editor.apply(); + } + + public void saveFloat(String key, float value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putFloat(key, value); + editor.apply(); + } + + public void saveLong(String key, long value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putLong(key, value); + editor.apply(); + } + + public void updateString(String key, String value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.contains(key)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(key, value); + editor.apply(); + } + } + + public void updateBool(String key, boolean value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.contains(key)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(key, value); + editor.apply(); + } + } + + public void updateInt(String key, int value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.contains(key)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putInt(key, value); + editor.apply(); + } + } + + public void updateFloat(String key, float value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.contains(key)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putFloat(key, value); + editor.apply(); + } + } + + public void updateLong(String key, long value) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.contains(key)) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putLong(key, value); + editor.apply(); + } + } + + public String getString(String key) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getString(key, null); + } + + public boolean getBool(String key) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getBoolean(key, false); + } + + public int getInt(String key) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getInt(key, 0); + } + + public float getFloat(String key) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getFloat(key, 0f); + } + + public long getLong(String key) { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + return sharedPreferences.getLong(key, 0L); + } + + public void clearPreferences() { + SharedPreferences sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + } +} diff --git a/app/src/main/play_store_512.png b/app/src/main/play_store_512.png new file mode 100644 index 0000000..f935e45 Binary files /dev/null and b/app/src/main/play_store_512.png differ diff --git a/app/src/main/res/drawable/button.xml b/app/src/main/res/drawable/button.xml new file mode 100644 index 0000000..d0667c4 --- /dev/null +++ b/app/src/main/res/drawable/button.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/clubelec_integration.xml b/app/src/main/res/drawable/clubelec_integration.xml new file mode 100644 index 0000000..4407fb5 --- /dev/null +++ b/app/src/main/res/drawable/clubelec_integration.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/undraw_subscribe.xml b/app/src/main/res/drawable/undraw_subscribe.xml new file mode 100644 index 0000000..c55a66d --- /dev/null +++ b/app/src/main/res/drawable/undraw_subscribe.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/font/varela_round_regular.ttf b/app/src/main/res/font/varela_round_regular.ttf new file mode 100644 index 0000000..5d5cfdd Binary files /dev/null and b/app/src/main/res/font/varela_round_regular.ttf differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f75acf2 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + +