├── app
├── .gitignore
├── src
│ └── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── remove.png
│ │ │ ├── logo_oco.png
│ │ │ ├── logo_vendor.png
│ │ │ ├── logo_ballbreak.png
│ │ │ ├── logo_fsclock.png
│ │ │ ├── logo_customerdb.png
│ │ │ ├── logo_masterplan.png
│ │ │ ├── logo_vendor_text.png
│ │ │ ├── logo_customerdb_raw.png
│ │ │ ├── logo_remotepointer.png
│ │ │ ├── background_spinner_arrow.xml
│ │ │ ├── ic_add_green_36dp.xml
│ │ │ ├── ic_add_white_36dp.xml
│ │ │ ├── ic_check_white_24dp.xml
│ │ │ ├── ic_tick_green_24dp.xml
│ │ │ ├── ic_add_dynamic_36dp.xml
│ │ │ ├── ic_warning_orange_24dp.xml
│ │ │ ├── ic_filter_list_white_24dp.xml
│ │ │ ├── border.xml
│ │ │ ├── image_border.xml
│ │ │ ├── ic_import_export_white_24dp.xml
│ │ │ ├── ic_star_gold_24dp.xml
│ │ │ ├── ic_fail_red_36dp.xml
│ │ │ ├── ic_person_black_96dp.xml
│ │ │ ├── ic_baseline_arrow_forward_dynamic_24dp.xml
│ │ │ ├── ic_close_white_24dp.xml
│ │ │ ├── ic_baseline_arrow_back_dynamic_24dp.xml
│ │ │ ├── ic_markunread_mailbox_white_24dp.xml
│ │ │ ├── ic_edit_white_24dp.xml
│ │ │ ├── info_outline.xml
│ │ │ ├── ic_copy_white_24dp.xml
│ │ │ ├── ic_baseline_clear_dynamic_24dp.xml
│ │ │ ├── ic_info_outline_white_24dp.xml
│ │ │ ├── ic_print_white_24dp.xml
│ │ │ ├── ic_more_vert_gray_24dp.xml
│ │ │ ├── ic_baseline_person_dynamic_24dp.xml
│ │ │ ├── ic_baseline_calendar_24dp.xml
│ │ │ ├── ic_power_white_24dp.xml
│ │ │ ├── ic_delete_forever_black_24dp.xml
│ │ │ ├── ic_settings_overscan_white_24dp.xml
│ │ │ ├── ic_search_white_24dp.xml
│ │ │ ├── ic_sync_white_24dp.xml
│ │ │ ├── ic_sort_by_alpha_white_24dp.xml
│ │ │ ├── ic_lock_gray_24dp.xml
│ │ │ ├── ic_lock_outline_white_24dp.xml
│ │ │ ├── ic_lock_open_dynamic_24dp.xml
│ │ │ ├── ic_help_outline_white_24dp.xml
│ │ │ ├── ic_sync_problem_white_24dp.xml
│ │ │ ├── ic_people_white_24dp.xml
│ │ │ ├── ic_baseline_attach_file_24dp.xml
│ │ │ ├── ic_sync_dynamic_24dp.xml
│ │ │ ├── ic_attach_email_white_24dp.xml
│ │ │ ├── ic_baseline_people_dynamic_24dp.xml
│ │ │ ├── ic_card_giftcard_white_24dp.xml
│ │ │ ├── ic_cake_dynamic_24dp.xml
│ │ │ ├── background_spinner.xml
│ │ │ ├── ic_settings_white_24dp.xml
│ │ │ └── ic_customerdb_gray.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── color
│ │ │ ├── color_button_text.xml
│ │ │ ├── checkbox_text_color.xml
│ │ │ └── color_button_background.xml
│ │ ├── xml
│ │ │ ├── file_paths.xml
│ │ │ └── shortcuts.xml
│ │ ├── color-night
│ │ │ ├── color_button_text.xml
│ │ │ └── color_button_background.xml
│ │ ├── layout
│ │ │ ├── item_list_simple.xml
│ │ │ ├── item_simple_button.xml
│ │ │ ├── item_file_list.xml
│ │ │ ├── dialog_syncprogress.xml
│ │ │ ├── dialog_input_box.xml
│ │ │ ├── dialog_unlock_password.xml
│ │ │ ├── dialog_delete_account.xml
│ │ │ ├── dialog_reset_password.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── item_appointment.xml
│ │ │ ├── item_file_edit.xml
│ │ │ ├── activity_scan.xml
│ │ │ ├── dialog_set_password.xml
│ │ │ ├── activity_birthday.xml
│ │ │ ├── dialog_list.xml
│ │ │ ├── activity_draw.xml
│ │ │ ├── activity_text_view.xml
│ │ │ ├── item_list_customer.xml
│ │ │ ├── dialog_new_custom_field.xml
│ │ │ ├── dialog_register.xml
│ │ │ ├── dialog_news.xml
│ │ │ ├── dialog_customer_image.xml
│ │ │ ├── dialog_sort.xml
│ │ │ ├── dialog_file_add.xml
│ │ │ ├── dialog_export_single_customer.xml
│ │ │ ├── dialog_export_single_appointment.xml
│ │ │ ├── dialog_otherapps.xml
│ │ │ ├── dialog_send_newsletter.xml
│ │ │ ├── activity_main_navheader.xml
│ │ │ ├── dialog_new_calendar.xml
│ │ │ ├── dialog_voucher_redeem.xml
│ │ │ ├── dialog_color.xml
│ │ │ ├── dialog_filter.xml
│ │ │ └── dialog_import_export_voucher.xml
│ │ ├── values-night
│ │ │ └── colors.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── menu
│ │ │ ├── menu_customer_edit.xml
│ │ │ ├── menu_voucher_edit.xml
│ │ │ ├── menu_about.xml
│ │ │ ├── menu_draw.xml
│ │ │ ├── menu_text_view.xml
│ │ │ ├── menu_settings.xml
│ │ │ ├── menu_main.xml
│ │ │ ├── bottomnav_main.xml
│ │ │ ├── menu_customer_details.xml
│ │ │ ├── menu_calendar_appointment_edit.xml
│ │ │ ├── menu_voucher_details.xml
│ │ │ └── drawer_main.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── java
│ │ └── de
│ │ └── georgsieber
│ │ └── customerdb
│ │ ├── model
│ │ ├── CustomerFile.java
│ │ ├── CustomField.java
│ │ └── CustomerCalendar.java
│ │ ├── tools
│ │ ├── DateControl.java
│ │ ├── NumTools.java
│ │ ├── BitmapCompressor.java
│ │ ├── ColorControl.java
│ │ ├── StorageControl.java
│ │ └── CommonDialog.java
│ │ ├── print
│ │ └── PrintTools.java
│ │ ├── CustomerDatabaseApp.java
│ │ ├── CustomerAdapterBirthday.java
│ │ ├── CustomerComparator.java
│ │ ├── DrawActivity.java
│ │ ├── CalendarAppointmentView.java
│ │ ├── importexport
│ │ ├── QuotedPrintable.java
│ │ ├── CalendarCsvBuilder.java
│ │ └── CustomerCsvBuilder.java
│ │ ├── PhoneStateReceiver2.java
│ │ ├── DrawingView.java
│ │ ├── CustomerAdapter.java
│ │ ├── TextViewActivity.java
│ │ └── VoucherAdapter.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── fastlane
└── metadata
│ └── android
│ ├── de
│ ├── short_description.txt
│ ├── images
│ │ └── phoneScreenshots
│ │ │ ├── 01.png
│ │ │ ├── 02.png
│ │ │ ├── 03.png
│ │ │ ├── 04.png
│ │ │ ├── 05.png
│ │ │ ├── 06.jpg
│ │ │ ├── 07.png
│ │ │ └── 08.png
│ └── full_description.txt
│ └── en-US
│ ├── short_description.txt
│ ├── images
│ ├── icon.png
│ ├── featureGraphic.png
│ └── phoneScreenshots
│ │ ├── 01.png
│ │ ├── 02.png
│ │ ├── 03.png
│ │ ├── 04.png
│ │ ├── 05.png
│ │ ├── 06.jpg
│ │ ├── 07.png
│ │ └── 08.png
│ └── full_description.txt
├── .github
├── screenshot.png
└── FUNDING.yml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── README.md
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/short_description.txt:
--------------------------------------------------------------------------------
1 | Einfache Kundendatenverwaltung
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Simple Customer Management
--------------------------------------------------------------------------------
/.github/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/.github/screenshot.png
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/remove.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_oco.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_oco.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_vendor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_vendor.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_ballbreak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_ballbreak.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_fsclock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_fsclock.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_customerdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_customerdb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_masterplan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_masterplan.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_vendor_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_vendor_text.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_customerdb_raw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_customerdb_raw.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/logo_remotepointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/drawable/logo_remotepointer.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/01.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/02.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/03.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/04.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/05.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/06.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/07.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/images/phoneScreenshots/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/de/images/phoneScreenshots/08.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/featureGraphic.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/06.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/06.jpg
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schorschii/CustomerDB-Android/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: ['schorschii']
2 | liberapay: schorschii
3 | custom: ['https://www.paypal.me/schorschii', 'https://play.google.com/store/apps/details?id=de.georgsieber.customerdb']
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | /assets
12 | /release
13 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_button_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color-night/color_button_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Nov 01 18:37:21 CET 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color-night/color_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_spinner_arrow.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/model/CustomerFile.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.model;
2 |
3 | public class CustomerFile {
4 | public String mName;
5 | public byte[] mContent;
6 |
7 | public CustomerFile(String _name, byte[] _content) {
8 | mName = _name;
9 | mContent = _content;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_green_36dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_white_36dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tick_green_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_dynamic_36dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_warning_orange_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_filter_list_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/border.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_list_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/image_border.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_import_export_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_gold_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5EA3B9
4 | #181818
5 | #fff
6 | #BABABA
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_fail_red_36dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_person_black_96dp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_forward_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_back_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_markunread_mailbox_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/color/checkbox_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/info_outline.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_copy_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_clear_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_outline_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_print_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_more_vert_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_person_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_calendar_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_power_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_forever_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_customer_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_voucher_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/color/color_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_overscan_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sync_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sort_by_alpha_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_simple_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lock_gray_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lock_outline_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_lock_open_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/DateControl.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import java.text.DateFormat;
4 | import java.util.Date;
5 | import java.util.Locale;
6 |
7 | public class DateControl {
8 |
9 | public static DateFormat birthdayDateFormat = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault());
10 | public static DateFormat displayDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
11 | public static DateFormat displayTimeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault());
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help_outline_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sync_problem_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_people_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_attach_file_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sync_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_attach_email_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_people_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_draw.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 16dp
4 | 16dp
5 |
6 | 1dp
7 | 85dp
8 | 30dp
9 | 5dp
10 |
11 | 8dp
12 | 18dp
13 | 7dp
14 | 2dp
15 | 2dp
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_text_view.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #0F7C9D
4 |
5 | #0f7c9d
6 | #0f7c9d
7 | #0f7c9d
8 | #fff
9 | #737373
10 | #126C88
11 | #999
12 | #FAFAFA
13 | #222
14 | #757575
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_card_giftcard_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_file_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | android.enableJetifier=true
13 | android.useAndroidX=true
14 | org.gradle.jvmargs=-Xmx1536m
15 |
16 | # When configured, Gradle will run in incubating parallel mode.
17 | # This option should only be used with decoupled projects. More details, visit
18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
19 | # org.gradle.parallel=true
20 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/model/CustomField.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.model;
2 |
3 | import android.view.View;
4 |
5 | public class CustomField {
6 | public int mId = -1;
7 | public String mTitle = "";
8 | public int mType = -1;
9 | public String mValue;
10 | public View mEditViewReference;
11 |
12 | public CustomField(String title, int type) {
13 | this.mTitle = title;
14 | this.mType = type;
15 | }
16 |
17 | public CustomField(int id, String title, int type) {
18 | this.mId = id;
19 | this.mTitle = title;
20 | this.mType = type;
21 | }
22 |
23 | public CustomField(String title, String value) {
24 | this.mTitle = title;
25 | this.mValue = value;
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return mTitle;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottomnav_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cake_dynamic_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_syncprogress.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
17 |
18 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_input_box.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
18 |
19 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/print/PrintTools.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.print;
2 |
3 | public class PrintTools {
4 | static String wordWrap(final String input, final int length) {
5 | if(input == null || length < 1) {
6 | throw new IllegalArgumentException("Invalid input args");
7 | }
8 | final String text = input.trim();
9 | if(text.length() > length && text.contains(" ")) {
10 | final String line = text.substring(0, length);
11 | final int lineBreakIndex = line.indexOf("\n");
12 | final int lineLastSpaceIndex = line.lastIndexOf(" ");
13 | final int inputFirstSpaceIndex = text.indexOf(" ");
14 | final int breakIndex = lineBreakIndex > -1 ? lineBreakIndex :
15 | (lineLastSpaceIndex > -1 ? lineLastSpaceIndex : inputFirstSpaceIndex);
16 | return text.substring(0, breakIndex) + "\n" + wordWrap(text.substring(breakIndex + 1), length);
17 | } else {
18 | return text;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_customer_details.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
15 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_unlock_password.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
18 |
19 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_delete_account.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_reset_password.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
9 |
10 |
11 |
14 |
19 |
24 |
25 |
26 | -
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
20 |
21 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_appointment.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
15 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_file_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_scan.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_calendar_appointment_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
16 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_set_password.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
15 |
16 |
17 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_birthday.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Customer Database Android
2 | [](https://play.google.com/store/apps/details?id=de.georgsieber.customerdb)
3 | [](https://apt.izzysoft.de/fdroid/index/apk/de.georgsieber.customerdb)
4 | [](https://github.com/schorschii/customerdb-Android/releases)
5 |
6 | With this Android app you can manage your customers separated from the private contacts, business appointments and vouchers. It is also available for [iOS](https://github.com/schorschii/customerdb-ios).
7 |
8 | The goal of this project is to provide an easy-to-use, platform independent, open source CRM application for small businesses without tracking or forced cloud connection. To enable non-technical users to use the sync feature, a cloud service is provided optionally. If you like this project, please support the development by purchasing one of the in-app purchases in the Play Store or via Github sponsoring, if you use the direct APK download.
9 |
10 | You can set up your own self-hosted sync server using the [Customer Database Server](https://github.com/schorschii/customerdb-server), which is also open source.
11 |
12 | Contributions welcome :-)
13 |
14 | ## Screenshots
15 | 
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
27 |
28 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_voucher_details.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
15 |
22 |
27 |
32 |
33 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | defaultConfig {
5 | applicationId "de.georgsieber.customerdb"
6 | minSdk 15
7 | targetSdk 34
8 | compileSdk 34
9 | versionCode 99
10 | versionName "3.11.6"
11 | multiDexEnabled true
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | applicationVariants.all { variant ->
19 | variant.outputs.all { output ->
20 | output.outputFileName = 'customerdb.apk'
21 | }
22 | }
23 | }
24 | }
25 | namespace 'de.georgsieber.customerdb'
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | implementation 'androidx.appcompat:appcompat:1.6.1'
31 | implementation 'com.opencsv:opencsv:4.6'
32 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
33 | implementation 'com.google.android.material:material:1.9.0'
34 | implementation 'androidx.gridlayout:gridlayout:1.0.0'
35 | implementation 'com.android.billingclient:billing:6.0.1'
36 | implementation 'me.dm7.barcodescanner:zxing:1.9.8'
37 | }
38 |
39 | configurations {
40 | all {
41 | exclude module: 'commons-logging'
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/CustomerDatabaseApp.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.content.res.Configuration;
7 |
8 | import androidx.appcompat.app.AppCompatDelegate;
9 |
10 | public class CustomerDatabaseApp extends Application {
11 |
12 | @Override
13 | public void onCreate() {
14 | setAppTheme(getAppTheme(getApplicationContext()));
15 | super.onCreate();
16 | }
17 |
18 | public static void setAppTheme(int setting) {
19 | AppCompatDelegate.setDefaultNightMode(setting);
20 | }
21 |
22 | public static int getAppTheme(Context context) {
23 | SharedPreferences prefs = context.getSharedPreferences(MainActivity.PREFS_NAME, 0);
24 | return prefs.getInt("dark-mode-native", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
25 | }
26 |
27 | public static boolean isDarkThemeActive(Context context, int setting) {
28 | if(setting == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
29 | return isDarkThemeActive(context);
30 | } else {
31 | return setting == AppCompatDelegate.MODE_NIGHT_YES;
32 | }
33 | }
34 |
35 | public static boolean isDarkThemeActive(Context context) {
36 | int uiMode = context.getResources().getConfiguration().uiMode;
37 | return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/NumTools.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import android.content.Context;
4 |
5 | import java.text.NumberFormat;
6 | import java.util.Locale;
7 |
8 | public class NumTools {
9 | public static Long tryParseNullableLong(String value, Long defaultVal) {
10 | try {
11 | return Long.parseLong(value);
12 | } catch (NumberFormatException e) {
13 | return defaultVal;
14 | }
15 | }
16 | public static long tryParseLong(String value, long defaultVal) {
17 | try {
18 | return Long.parseLong(value);
19 | } catch (NumberFormatException e) {
20 | return defaultVal;
21 | }
22 | }
23 | public static Double tryParseDouble(String str) {
24 | try {
25 | NumberFormat format = NumberFormat.getInstance(Locale.getDefault());
26 | Number number = format.parse(str);
27 | return number.doubleValue();
28 | //return Double.parseDouble(str);
29 | }
30 | catch(Exception e) {
31 | return null;
32 | }
33 | }
34 | public static Integer tryParseInt(String str) {
35 | try {
36 | return Integer.parseInt(str);
37 | }
38 | catch(Exception e) {
39 | return null;
40 | }
41 | }
42 |
43 | @SuppressWarnings("SameParameterValue")
44 | public static int dpToPx(int dp, Context c) {
45 | return (int)(dp * c.getResources().getDisplayMetrics().density);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | This simple customer database application is designed for small companies for easy and secure customer management on smartphones and tablets, separated from private contacts.
It is possible to sync your data with a (self-hosted) MySQL server to prevent loss or defect of your mobile device. This also allows to sync the data with multiple devices.
In addition to that, it has tools for the EU-GDPR and you can group customers, manage customers birthdays and vouchers, write newsletters, import customers from VCF files, export your data into VCF or CSV files and print a customer record (PDF export). Additionally, this app has a Dark Mode and the ability to save customer images.
The app shows you the name of the customer for incoming calls if the phone number is stored in the customer database (caller identification / caller ID).
Because of its high flexibility, the application is also often used in non-enterprise areas, such as teachers’ management of parent contact information.
Additional features that can be unlocked via in-app purchase (Google Billing):
Custom Fields: also drop down selection lists and sorting by these fields is supported. "Input-Only-Mode": Optional activatable mode that allows only data entry. For example, the device can be handed to showroom visitors. Calendar: Manage customer appointments. Customers can insert the appointment directly into their calendar using a QR code. File Attachments: Attach up to 20 files to a customer. Design Options: Allows you to set your own title bar color and own startup logo.
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/BitmapCompressor.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.util.Log;
6 |
7 | import java.io.File;
8 |
9 | public class BitmapCompressor {
10 |
11 | private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
12 | // Raw height and width of mImage
13 | int height = options.outHeight;
14 | int width = options.outWidth;
15 | int inSampleSize = 1;
16 |
17 | while ((width/inSampleSize) > reqWidth || (height/inSampleSize) > reqHeight) {
18 | inSampleSize += 1;
19 | }
20 |
21 | Log.i("BitmapCompressor", Integer.toString(inSampleSize));
22 | return inSampleSize;
23 | }
24 |
25 | public static Bitmap getSmallBitmap(File file) {
26 |
27 | long originalSize = file.length();
28 |
29 | Log.i("BitmapCompressor", "Original mImage size is: " + originalSize + " bytes.");
30 |
31 | final BitmapFactory.Options options = new BitmapFactory.Options();
32 | options.inJustDecodeBounds = true;
33 | BitmapFactory.decodeFile(file.getAbsolutePath(), options);
34 |
35 | // Calculate inSampleSize based on a preset ratio
36 | options.inSampleSize = calculateInSampleSize(options, 850, 700);
37 |
38 | // Decode bitmap with inSampleSize set
39 | options.inJustDecodeBounds = false;
40 |
41 | Bitmap compressedImage = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
42 |
43 | Log.i("BitmapCompressor", "Compressed mImage size is " + compressedImage.getByteCount() + " bytes");
44 |
45 | return compressedImage;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_draw.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
29 |
30 |
34 |
35 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_text_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
19 |
20 |
21 |
22 |
29 |
30 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de/full_description.txt:
--------------------------------------------------------------------------------
1 | Mit dieser Kundendatenbank können kleine Unternehmen ihre Kunden einfach und sicher auf einem Smartphone oder Tablet verwalten, getrennt von den privaten Kontakten.
Es ist möglich die Daten mit einem (eigenen) MySQL-Server abzugleichen, damit sie vor Verlust oder Defekt des mobilen Endgerätes geschützt sind. Dadurch können die Daten auch auf mehrere Geräte synchronisiert werden.
Zusätzlich gibt es Funktionen für die EU-DSGVO, die Möglichkeit Kunden zu gruppieren und Gutscheine zu verwalten, eine Newsletter- und Geburtstagsfunktion, eine Druckfunktion (PDF-Export) sowie die Möglichkeit Daten aus VCF-Dateien zu importieren und Kunden in eine VCF- oder CSV-Datei zu exportieren. Des Weiteren besitzt die App einen Dark Mode und die Möglichkeit Kundenbilder mit abzuspeichern.
Außerdem zeigt Ihnen die App bei eingehenden Anrufen den Namen des Kunden an, sofern die Telefonnummer in der Kundendatenbank gespeichert ist (Anruferidentifizierung).
Aufgrund der hohen Flexibilität wird die Anwendung auch gerne in Bereichen außerhalb von Unternehmen eingesetzt, zum Beispiel von Lehrern zur Verwaltung der Kontaktdaten der Eltern.
Zusätzliche Funktionen, freischaltbar via In-App-Einkauf (Google Billing):
Eigene, benutzerdefinierte Felder hinzufügen. Auch Auswahllisten und Sortierung nach den eigenen Feldern ist möglich. "Nur-Eingabe-Modus": Optional aktivierbarer Modus, bei dem alle Funktionen bis auf das Anlegen neuer Kunden gesperrt sind. Dadurch kann das Gerät dem Kunden übergeben werden, damit er sich selbst eintragen kann. Kalenderfunktion: zur Verwaltung von Kundenterminen, z.B. für Friseure. Kunden können den Termin via QR-Code direkt in ihren Kalender eintragen. Dateianhänge: Hängen Sie bis zu 20 Dateien an einen Kunden an, z.B. Angebote. Design-Optionen: Ermöglicht Ihnen das Anpassen der Farbe an Ihr Corporate Design und die Verwendung eigenen Logos.
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/CustomerAdapterBirthday.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.BaseAdapter;
8 | import android.widget.CheckBox;
9 | import android.widget.TextView;
10 |
11 | import java.util.List;
12 |
13 | import de.georgsieber.customerdb.model.Customer;
14 |
15 | public class CustomerAdapterBirthday extends BaseAdapter {
16 | private Context mContext;
17 | private List mCustomers;
18 |
19 | CustomerAdapterBirthday(Context context, List customers) {
20 | mContext = context;
21 | mCustomers = customers;
22 | }
23 |
24 | @Override
25 | public int getCount() {
26 | return mCustomers.size();
27 | }
28 |
29 | @Override
30 | public Object getItem(int position) {
31 | return mCustomers.get(position);
32 | }
33 |
34 | @Override
35 | public long getItemId(int position) {
36 | return 0;
37 | }
38 |
39 | @Override
40 | public View getView(int position, View convertView, ViewGroup parent) {
41 | if(convertView == null) {
42 | LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
43 | convertView = inflater.inflate(R.layout.item_list_customer, null);
44 | }
45 |
46 | TextView tv1 = (convertView.findViewById(R.id.textViewCustomerListItem1));
47 | TextView tv2 = (convertView.findViewById(R.id.textViewCustomerListItem2));
48 | CheckBox cb = (convertView.findViewById(R.id.checkBoxCustomerListItem));
49 |
50 | tv1.setText(mCustomers.get(position).getFirstLine());
51 | tv2.setText(mCustomers.get(position).getBirthdayString(mContext.getResources().getString(R.string.birthdaytodaynote)));
52 | cb.setVisibility(View.GONE);
53 |
54 | return convertView;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_list_customer.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
17 |
18 |
21 |
22 |
29 |
30 |
38 |
39 |
42 |
43 |
44 |
50 |
51 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/ColorControl.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import android.content.SharedPreferences;
4 | import android.content.res.ColorStateList;
5 | import android.graphics.Color;
6 | import android.graphics.drawable.ColorDrawable;
7 | import android.os.Build;
8 | import androidx.appcompat.app.ActionBar;
9 | import androidx.appcompat.app.AppCompatActivity;
10 | import android.view.View;
11 |
12 | public class ColorControl {
13 | public final static int DEFAULT_COLOR_R = 15;
14 | public final static int DEFAULT_COLOR_G = 124;
15 | public final static int DEFAULT_COLOR_B = 157;
16 |
17 | public static int getColorFromSettings(SharedPreferences settings) {
18 | int r = settings.getInt("design-red", DEFAULT_COLOR_R);
19 | int g = settings.getInt("design-green", DEFAULT_COLOR_G);
20 | int b = settings.getInt("design-blue", DEFAULT_COLOR_B);
21 | return Color.argb(0xff,r,g,b);
22 | }
23 |
24 | public static void updateActionBarColor(AppCompatActivity a, SharedPreferences settings) {
25 | int color = getColorFromSettings(settings);
26 | ActionBar ab = a.getSupportActionBar();
27 | if(ab != null) ab.setBackgroundDrawable(new ColorDrawable(color));
28 | if(Build.VERSION.SDK_INT >= 21) {
29 | a.getWindow().setStatusBarColor(color);
30 | }
31 | }
32 |
33 | public static void updateAccentColor(View v, SharedPreferences settings) {
34 | int color = getColorFromSettings(settings);
35 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
36 | v.setBackgroundTintList(ColorStateList.valueOf(color));
37 | } else {
38 | v.setBackgroundDrawable(new ColorDrawable(color));
39 | }
40 | }
41 |
42 | public static boolean isColorDark(int color) {
43 | double darkness = 1-(0.299*Color.red(color) + 0.587*Color.green(color) + 0.114*Color.blue(color))/255;
44 | return !(darkness < 0.5);
45 | }
46 |
47 | public static String getHexColor(int color) {
48 | return String.format("#%06X", (0xFFFFFF & color));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_customerdb_gray.xml:
--------------------------------------------------------------------------------
1 |
6 |
16 |
25 |
35 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_new_custom_field.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
22 |
28 |
33 |
38 |
43 |
48 |
49 |
50 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_register.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
20 |
26 |
30 |
33 |
38 |
47 |
48 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_news.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
17 |
18 |
23 |
24 |
27 |
28 |
33 |
38 |
43 |
44 |
47 |
48 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_customer_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
29 |
30 |
33 |
34 |
40 |
46 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/CustomerComparator.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import java.util.Comparator;
4 | import java.util.List;
5 |
6 | import de.georgsieber.customerdb.model.CustomField;
7 | import de.georgsieber.customerdb.model.Customer;
8 |
9 | public class CustomerComparator implements Comparator {
10 |
11 | private FIELD mSortField;
12 | private String mSortCustomField;
13 | private boolean mAscending;
14 |
15 | enum FIELD {
16 | TITLE,
17 | FIRST_NAME,
18 | LAST_NAME,
19 | LAST_MODIFIED
20 | }
21 |
22 | CustomerComparator(String customFieldTitleForSort, boolean ascending) {
23 | this.mSortCustomField = customFieldTitleForSort;
24 | this.mAscending = ascending;
25 | }
26 |
27 | CustomerComparator(FIELD field, boolean ascending) {
28 | this.mSortField = field;
29 | this.mAscending = ascending;
30 | }
31 |
32 | @Override
33 | public int compare(Customer c1, Customer c2) {
34 | String fieldValue_1 = "";
35 | String fieldValue_2 = "";
36 |
37 | if(mSortCustomField != null) {
38 | List cf_1 = c1.getCustomFields();
39 | List cf_2 = c2.getCustomFields();
40 | for(CustomField cf : cf_1) {
41 | if(cf.mTitle.equals(mSortCustomField))
42 | fieldValue_1 = cf.mValue;
43 | }
44 | for(CustomField cf : cf_2) {
45 | if(cf.mTitle.equals(mSortCustomField))
46 | fieldValue_2 = cf.mValue;
47 | }
48 | }
49 | else if(mSortField != null) {
50 | if(mSortField == FIELD.TITLE) {
51 | fieldValue_1 = c1.mTitle;
52 | fieldValue_2 = c2.mTitle;
53 | }
54 | else if(mSortField == FIELD.FIRST_NAME) {
55 | fieldValue_1 = c1.mFirstName;
56 | fieldValue_2 = c2.mFirstName;
57 | }
58 | else if(mSortField == FIELD.LAST_NAME) {
59 | fieldValue_1 = c1.mLastName;
60 | fieldValue_2 = c2.mLastName;
61 | }
62 | else if(mSortField == FIELD.LAST_MODIFIED) {
63 | fieldValue_1 = CustomerDatabase.dateToString(c1.mLastModified);
64 | fieldValue_2 = CustomerDatabase.dateToString(c2.mLastModified);
65 | }
66 | }
67 |
68 | if(mAscending) return fieldValue_1.compareTo(fieldValue_2);
69 | else return fieldValue_2.compareTo(fieldValue_1);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/DrawActivity.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.Intent;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Color;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.appcompat.widget.Toolbar;
8 |
9 | import android.os.Bundle;
10 | import android.util.Base64;
11 | import android.view.Menu;
12 | import android.view.MenuItem;
13 | import android.view.View;
14 |
15 | import java.io.ByteArrayOutputStream;
16 |
17 | public class DrawActivity extends AppCompatActivity {
18 |
19 | private DrawingView dv;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | // init activity view
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_draw);
26 | dv = findViewById(R.id.drawingView);
27 |
28 | // init toolbar
29 | Toolbar toolbar = findViewById(R.id.toolbar);
30 | setSupportActionBar(toolbar);
31 | if(getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true);
32 | }
33 |
34 | @Override
35 | public boolean onCreateOptionsMenu(Menu menu) {
36 | getMenuInflater().inflate(R.menu.menu_draw, menu);
37 | return true;
38 | }
39 |
40 | @Override
41 | public boolean onOptionsItemSelected(MenuItem item) {
42 | switch(item.getItemId()) {
43 | case android.R.id.home:
44 | finish();
45 | break;
46 | case R.id.action_done:
47 | Intent data = new Intent();
48 | data.putExtra("image", bitmapToBase64(bitmapFromView(dv)));
49 | setResult(RESULT_OK, data);
50 | finish();
51 | break;
52 | case R.id.action_abort:
53 | setResult(-1);
54 | finish();
55 | break;
56 | case R.id.action_clear:
57 | dv.mCanvas.drawColor(Color.WHITE);
58 | dv.invalidate();
59 | break;
60 | }
61 | return true;
62 | }
63 |
64 | private String bitmapToBase64(Bitmap b) {
65 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
66 | b.compress(Bitmap.CompressFormat.JPEG, 90, byteArrayOutputStream);
67 | byte[] byteArray = byteArrayOutputStream.toByteArray();
68 | return Base64.encodeToString(byteArray, Base64.NO_WRAP);
69 | }
70 |
71 | public Bitmap bitmapFromView(View v) {
72 | Bitmap bitmap;
73 | v.setDrawingCacheEnabled(true);
74 | bitmap = Bitmap.createBitmap(v.getDrawingCache());
75 | v.setDrawingCacheEnabled(false);
76 | return bitmap;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/drawer_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | -
8 |
9 |
13 |
17 |
18 |
19 |
20 | -
21 |
22 |
26 |
30 |
31 |
32 |
33 | -
34 |
35 |
39 |
43 |
47 |
51 |
55 |
59 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_sort.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
18 |
23 |
28 |
33 |
34 |
40 |
43 |
49 |
53 |
54 |
60 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_file_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
29 |
30 |
33 |
34 |
40 |
46 |
52 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/CalendarAppointmentView.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.graphics.drawable.GradientDrawable;
6 | import android.os.Build;
7 | import android.util.AttributeSet;
8 | import android.widget.LinearLayout;
9 | import android.widget.TextView;
10 |
11 | import de.georgsieber.customerdb.tools.ColorControl;
12 |
13 | public class CalendarAppointmentView extends LinearLayout {
14 | public CalendarAppointmentView(Context context) {
15 | super(context);
16 | }
17 | public CalendarAppointmentView(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 | public CalendarAppointmentView(Context context, AttributeSet attrs, int defStyle) {
21 | super(context, attrs, defStyle);
22 | }
23 |
24 | public void setValues(String text, String subtitle, String time, int backgroundColor) {
25 | TextView textViewTitle = findViewById(R.id.textViewAppointmentTitle);
26 | if(text.equals("")) textViewTitle.setVisibility(GONE);
27 | textViewTitle.setText(text);
28 |
29 | TextView textViewSubtitle = findViewById(R.id.textViewAppointmentSubtitle);
30 | if(subtitle.equals("")) textViewSubtitle.setVisibility(GONE);
31 | textViewSubtitle.setText(subtitle);
32 |
33 | TextView textViewTime = findViewById(R.id.textViewAppointmentTime);
34 | if(time.equals("")) textViewTime.setVisibility(GONE);
35 | textViewTime.setText(time);
36 |
37 | if(ColorControl.isColorDark(backgroundColor)) {
38 | int colorSecondary = Color.argb(180, 255, 255, 255);
39 | textViewTitle.setTextColor(Color.WHITE);
40 | textViewSubtitle.setTextColor(colorSecondary);
41 | textViewTime.setTextColor(colorSecondary);
42 | } else {
43 | int colorSecondary = Color.argb(180, 0, 0, 0);
44 | textViewTitle.setTextColor(Color.BLACK);
45 | textViewSubtitle.setTextColor(colorSecondary);
46 | textViewTime.setTextColor(colorSecondary);
47 | }
48 |
49 | if(ColorControl.isColorDark(backgroundColor)) {
50 | textViewTitle.setTextColor(Color.WHITE);
51 | } else {
52 | textViewTitle.setTextColor(Color.BLACK);
53 | }
54 |
55 | GradientDrawable border = new GradientDrawable();
56 | border.setCornerRadius(12);
57 | border.setColor(backgroundColor);
58 | border.setAlpha(220);
59 | border.setStroke((int)getResources().getDimension(R.dimen.hour_divider_height), getResources().getColor(R.color.colorDivider));
60 | if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
61 | setBackgroundDrawable(border);
62 | } else {
63 | setBackground(border);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_export_single_customer.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
28 |
29 |
32 |
33 |
38 |
39 |
42 |
43 |
49 |
55 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/model/CustomerCalendar.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.model;
2 |
3 | import android.annotation.SuppressLint;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import java.text.DateFormat;
8 | import java.text.ParseException;
9 | import java.text.SimpleDateFormat;
10 | import java.util.Date;
11 | import java.util.concurrent.ThreadLocalRandom;
12 |
13 | import de.georgsieber.customerdb.CustomerDatabase;
14 | import de.georgsieber.customerdb.tools.NumTools;
15 |
16 | public class CustomerCalendar {
17 |
18 | public long mId = -1;
19 | public String mTitle = "";
20 | public String mColor = "";
21 | public String mNotes = "";
22 | public Date mLastModified = new Date();
23 | public int mRemoved = 0;
24 |
25 | public CustomerCalendar() {
26 | super();
27 | }
28 | public CustomerCalendar(long _id, String _title, String _color, String _notes, Date _lastModified, int _removed) {
29 | mId = _id;
30 | mTitle = _title;
31 | mColor = _color;
32 | mNotes = _notes;
33 | mLastModified = _lastModified;
34 | mRemoved = _removed;
35 | }
36 |
37 | @NonNull
38 | @Override
39 | public String toString() {
40 | return mTitle;
41 | }
42 |
43 | public static long generateID() {
44 | /* This function generates an unique mId for a new record.
45 | * Required in order to get unique ids over multiple devices,
46 | * which are not in sync with the central mysql server */
47 | @SuppressLint("SimpleDateFormat")
48 | DateFormat idFormat = new SimpleDateFormat("yyyyMMddkkmmss");
49 | String random;
50 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
51 | random = String.valueOf(ThreadLocalRandom.current().nextInt(1, 100));
52 | } else {
53 | random = "10";
54 | }
55 | return Long.parseLong(idFormat.format(new Date())+random);
56 | }
57 |
58 | public void putAttribute(String key, String value) {
59 | switch(key) {
60 | case "id":
61 | mId = NumTools.tryParseLong(value, mId);
62 | break;
63 | case "title":
64 | mTitle = value;
65 | break;
66 | case "color":
67 | mColor = value;
68 | break;
69 | case "notes":
70 | mNotes = value;
71 | break;
72 | case "last_modified":
73 | try {
74 | mLastModified = CustomerDatabase.parseDate(value);
75 | } catch (ParseException e) {
76 | e.printStackTrace();
77 | }
78 | break;
79 | case "removed":
80 | mRemoved = Integer.parseInt(value); break;
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_export_single_appointment.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
28 |
29 |
32 |
33 |
38 |
39 |
42 |
43 |
49 |
55 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/shortcuts.xml:
--------------------------------------------------------------------------------
1 |
3 |
11 |
15 |
16 |
17 |
25 |
29 |
30 |
31 |
39 |
43 |
44 |
45 |
53 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_otherapps.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
28 |
33 |
37 |
41 |
45 |
49 |
53 |
54 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/importexport/QuotedPrintable.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.importexport;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.ArrayList;
5 |
6 | class QuotedPrintable {
7 |
8 | static String ASCII_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 ";
9 |
10 | @SuppressWarnings("CharsetObjectCanBeUsed")
11 | static String decode(String str) {
12 | ArrayList bytes = new ArrayList<>();
13 | for(int i = 0; i < str.length(); i++) {
14 | char currentChar = str.charAt(i);
15 | try {
16 | if(currentChar == '=' && i+2 <= str.length()-1) {
17 | char nextChar1 = str.charAt(i+1);
18 | char nextChar2 = str.charAt(i+2);
19 | int val = Integer.parseInt(String.valueOf(nextChar1)+String.valueOf(nextChar2), 16);
20 | bytes.add((byte) val);
21 | i += 2;
22 | continue;
23 | }
24 | } catch(Exception ignored) {}
25 | bytes.add((byte) currentChar);
26 | }
27 | try {
28 | return new String(toByteArray(bytes), "UTF-8");
29 | } catch(UnsupportedEncodingException ignored) {
30 | return str;
31 | }
32 | }
33 |
34 | @SuppressWarnings("CharsetObjectCanBeUsed")
35 | static String encode(String str) {
36 | StringBuilder encoded = new StringBuilder();
37 | try {
38 | for(String s : splitToCodePoints(str)) {
39 | if(ASCII_CHARS.contains(s)) {
40 | // append standard ASCII chars directly (should be human readable)
41 | encoded.append(s);
42 | } else {
43 | // append all bytes of char quoted-printable encoded
44 | for(byte b : s.getBytes("UTF-8")) {
45 | encoded.append("=").append(String.format("%02X", b));
46 | }
47 | }
48 | }
49 | } catch(UnsupportedEncodingException ignored) {}
50 | return encoded.toString();
51 | }
52 |
53 | private static ArrayList splitToCodePoints(String str) {
54 | ArrayList list = new ArrayList<>();
55 | int count = 0;
56 | while(count < str.length()) {
57 | int cp = str.codePointAt(count);
58 | list.add(new String(Character.toChars(cp)));
59 | count += Character.charCount(cp);
60 | }
61 | return list;
62 | }
63 |
64 | private static byte[] toByteArray(ArrayList in) {
65 | final int n = in.size();
66 | byte[] ret = new byte[n];
67 | for (int i = 0; i < n; i++) {
68 | ret[i] = in.get(i);
69 | }
70 | return ret;
71 | }
72 |
73 | static boolean isVcfFieldQuotedPrintableEncoded(String[] fieldOptions) {
74 | for(String option : fieldOptions) {
75 | if(option.toUpperCase().equals("ENCODING=QUOTED-PRINTABLE")) {
76 | return true;
77 | }
78 | }
79 | return false;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/PhoneStateReceiver2.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.os.Handler;
9 | import android.util.Log;
10 | import android.widget.Toast;
11 |
12 | import java.text.SimpleDateFormat;
13 | import java.util.Date;
14 |
15 | import de.georgsieber.customerdb.model.Customer;
16 |
17 | public class PhoneStateReceiver2 extends BroadcastReceiver {
18 |
19 | @Override
20 | public void onReceive(final Context context, Intent intent) {
21 | // local database init
22 | CustomerDatabase localDBConn = new CustomerDatabase(context);
23 |
24 | if(intent.getExtras() != null) {
25 | String incomingNumber = intent.getExtras().getString("number");
26 | final Customer callingCustomer = localDBConn.getCustomerByNumber(incomingNumber);
27 | if(callingCustomer != null) {
28 | Log.d("cutomerdbphonedbg", "SHOW_INFO__FOUND");
29 | new Handler().postDelayed(new Runnable() {
30 | @Override
31 | public void run() {
32 | showCallInfo(context, callingCustomer);
33 | }
34 | }, 500);
35 | new Handler().postDelayed(new Runnable() {
36 | @Override
37 | public void run() {
38 | showCallInfo(context, callingCustomer);
39 | }
40 | }, 3500);
41 | new Handler().postDelayed(new Runnable() {
42 | @Override
43 | public void run() {
44 | showCallInfo(context, callingCustomer);
45 | }
46 | }, 7000);
47 | saveLastCallInfo(context, incomingNumber, callingCustomer.getFullName(false));
48 | } else {
49 | Log.d("cutomerdbphonedbg", "SHOW_INFO__NOT_FOUND");
50 | saveLastCallInfo(context, incomingNumber, null);
51 | }
52 | }
53 | }
54 |
55 | private void showCallInfo(Context context, Customer callingCustomer) {
56 | Toast.makeText(context,
57 | context.getResources().getString(R.string.app_name) + ":\n" + callingCustomer.getFullName(false) + " " + context.getResources().getString(R.string.calling),
58 | Toast.LENGTH_LONG)
59 | .show();
60 |
61 | }
62 |
63 | protected void saveLastCallInfo(Context c, String number, String customer) {
64 | @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
65 |
66 | String callInfo = sdf.format(new Date()) +" "+
67 | number +" ("+ (customer == null ? c.getResources().getString(R.string.no_customer_found) : customer) +")";
68 |
69 | SharedPreferences settings = c.getSharedPreferences(MainActivity.PREFS_NAME, 0);
70 | SharedPreferences.Editor editor = settings.edit();
71 | editor.putString("last-call-received", callInfo);
72 | editor.apply();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_send_newsletter.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
29 |
32 |
37 |
42 |
43 |
50 |
51 |
54 |
55 |
61 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_navheader.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
20 |
23 |
24 |
28 |
33 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
61 |
66 |
69 |
70 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_new_calendar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
24 |
25 |
30 |
31 |
35 |
36 |
45 |
46 |
50 |
51 |
60 |
61 |
65 |
66 |
75 |
76 |
77 |
82 |
83 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/StorageControl.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.InputStream;
10 | import java.io.OutputStream;
11 | import java.util.Date;
12 |
13 | import de.georgsieber.customerdb.R;
14 |
15 | public class StorageControl {
16 |
17 | private static String DIR_TEMP = "tmp";
18 | private static String DIR_EXPORT = "export";
19 |
20 | public static File getStorageLogo(Context c) {
21 | File exportDir = c.getExternalFilesDir(null);
22 | return new File(exportDir, "logo.png");
23 | }
24 | public static File getStorageExportCsv(Context c) {
25 | return getFile(DIR_EXPORT, "export.csv", c);
26 | }
27 | public static File getStorageExportVcf(Context c) {
28 | return getFile(DIR_EXPORT, "export.vcf", c);
29 | }
30 | public static File getStorageExportIcs(Context c) {
31 | return getFile(DIR_EXPORT, "export.ics", c);
32 | }
33 | public static File getStorageImageTemp(Context c) {
34 | return getFile(DIR_TEMP, "image.tmp.jpg", c);
35 | }
36 | public static File getStorageFileTemp(Context c, String filename) {
37 | return getFile(DIR_TEMP, filename, c);
38 | }
39 |
40 | private static File getFile(String dir, String filename, Context c) {
41 | File exportDir = new File(c.getExternalFilesDir(null), dir);
42 | exportDir.mkdirs();
43 | return new File(exportDir, filename);
44 | }
45 |
46 | public static void scanFile(File f, Context c) {
47 | Uri uri = Uri.fromFile(f);
48 | Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
49 | c.sendBroadcast(scanFileIntent);
50 | }
51 |
52 | public static String getNewPictureFilename(Context c) {
53 | return (c.getString(R.string.picture) + " " + DateControl.displayDateFormat.format(new Date())).replaceAll("[^A-Za-z0-9 ]", "_") + ".jpg";
54 | }
55 |
56 | public static String getNewDrawingFilename(Context c) {
57 | return (c.getString(R.string.drawing) + " " + DateControl.displayDateFormat.format(new Date())).replaceAll("[^A-Za-z0-9 ]", "_") + ".jpg";
58 | }
59 |
60 | public static void deleteTempFiles(Context c) {
61 | try {
62 | deleteFilesInDirectory(new File(c.getExternalFilesDir(null), DIR_TEMP), c);
63 | } catch(Exception ignored) {}
64 | }
65 |
66 | public static boolean deleteFilesInDirectory(File fileOrDirectory, Context c) {
67 | if(!fileOrDirectory.isDirectory()) return false;
68 | for(File child : fileOrDirectory.listFiles()) {
69 | if(!child.isDirectory()) {
70 | if(!child.delete()) return false;
71 | scanFile(child, c); // otherwise file still appears via MTP...
72 | }
73 | }
74 | return true;
75 | }
76 |
77 | public static void moveFile(File sourceFile, Uri destinationUri, Context c) throws Exception {
78 | InputStream in = new FileInputStream(sourceFile);
79 | OutputStream out = c.getContentResolver().openOutputStream(destinationUri);
80 |
81 | byte[] buffer = new byte[1024];
82 | int read;
83 | while((read = in.read(buffer)) != -1) {
84 | out.write(buffer, 0, read);
85 | }
86 | in.close();
87 |
88 | // write the output file
89 | out.flush();
90 | out.close();
91 |
92 | // delete the original file
93 | sourceFile.delete();
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_voucher_redeem.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
26 |
27 |
35 |
36 |
46 |
47 |
55 |
56 |
65 |
66 |
67 |
70 |
71 |
77 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
20 |
21 |
28 |
29 |
30 |
31 |
36 |
37 |
44 |
45 |
50 |
51 |
60 |
61 |
66 |
67 |
76 |
77 |
82 |
83 |
92 |
93 |
94 |
99 |
100 |
101 |
104 |
105 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/importexport/CalendarCsvBuilder.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.importexport;
2 |
3 | import android.util.Log;
4 |
5 | import com.opencsv.CSVReader;
6 | import com.opencsv.CSVWriter;
7 |
8 | import java.io.File;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.InputStreamReader;
12 | import java.io.StringWriter;
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | import de.georgsieber.customerdb.CustomerDatabase;
18 | import de.georgsieber.customerdb.model.Customer;
19 | import de.georgsieber.customerdb.model.CustomerAppointment;
20 |
21 | public class CalendarCsvBuilder {
22 |
23 | private List mAppointments = new ArrayList<>();
24 |
25 | public CalendarCsvBuilder(List _appointments) {
26 | mAppointments = _appointments;
27 | }
28 | public CalendarCsvBuilder(CustomerAppointment _appointment) {
29 | mAppointments.add(_appointment);
30 | }
31 |
32 | private String buildCsvContent(CustomerDatabase db) {
33 | StringWriter content = new StringWriter();
34 |
35 | CSVWriter csvWriter = new CSVWriter(content);
36 |
37 | List headers = new ArrayList<>(Arrays.asList(
38 | "id", "title", "notes", "time_start", "time_end", "fullday", "customer", "customer_id", "location", "last_modified"
39 | ));
40 | csvWriter.writeNext(headers.toArray(new String[0]));
41 |
42 | for(CustomerAppointment ca : mAppointments) {
43 | String customerText = "";
44 | if(ca.mCustomerId != null) {
45 | Customer relatedCustomer = db.getCustomerById(ca.mCustomerId, false, false);
46 | if(relatedCustomer != null) {
47 | customerText = relatedCustomer.getFullName(false);
48 | }
49 | } else {
50 | customerText = ca.mCustomer;
51 | }
52 | List values = new ArrayList<>(Arrays.asList(
53 | Long.toString(ca.mId),
54 | ca.mTitle,
55 | ca.mNotes,
56 | ca.mTimeStart==null ? "" : CustomerDatabase.dateToStringRaw(ca.mTimeStart),
57 | ca.mTimeEnd==null ? "" : CustomerDatabase.dateToStringRaw(ca.mTimeEnd),
58 | ca.mFullday ? "1" : "0",
59 | customerText,
60 | ca.mCustomerId==null ? "" : Long.toString(ca.mCustomerId),
61 | ca.mLocation,
62 | ca.mLastModified==null ? "" : CustomerDatabase.dateToString(ca.mLastModified)
63 | ));
64 | csvWriter.writeNext(values.toArray(new String[0]));
65 | }
66 |
67 | return content.toString();
68 | }
69 |
70 | public boolean saveCsvFile(File f, CustomerDatabase db) {
71 | try {
72 | FileOutputStream stream = new FileOutputStream(f);
73 | stream.write(buildCsvContent(db).getBytes());
74 | stream.close();
75 | return true;
76 | } catch (IOException e) {
77 | Log.e("Exception", "File write failed: " + e.toString());
78 | }
79 | return false;
80 | }
81 |
82 | public static List readCsvFile(InputStreamReader isr) throws Exception {
83 | List newAppointments = new ArrayList<>();
84 | CSVReader reader = new CSVReader(isr);
85 | String[] headers = new String[]{};
86 | String[] line;
87 | int counter = 0;
88 | while((line = reader.readNext()) != null) {
89 | try {
90 | if(counter == 0) {
91 | headers = line;
92 | } else {
93 | CustomerAppointment newAppointment = new CustomerAppointment();
94 | int counter2 = 0;
95 | for(String field : line) {
96 | //Log.e("CSV", headers[counter2] + " -> "+ field);
97 | newAppointment.putAttribute(headers[counter2], field);
98 | counter2 ++;
99 | }
100 | newAppointments.add(newAppointment);
101 | }
102 | } catch(Exception ignored) {}
103 | counter ++;
104 | }
105 | return newAppointments;
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/DrawingView.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.util.AttributeSet;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 |
13 | class DrawingView extends View {
14 |
15 | public int width;
16 | public int height;
17 | public Path mPath;
18 | private Paint mPaint;
19 | public Canvas mCanvas;
20 | private Bitmap mBitmap;
21 | private Paint mBitmapPaint;
22 | private Paint circlePaint;
23 | private Path circlePath;
24 | private Context context;
25 |
26 | public DrawingView(Context c) {
27 | super(c);
28 | context = c;
29 | init();
30 | }
31 | public DrawingView(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | init();
34 | }
35 | public DrawingView(Context context, AttributeSet attrs, int defStyle) {
36 | super(context, attrs, defStyle);
37 | init();
38 | }
39 |
40 | private void init() {
41 | mPath = new Path();
42 | mBitmapPaint = new Paint(Paint.DITHER_FLAG);
43 | circlePaint = new Paint();
44 | circlePath = new Path();
45 | circlePaint.setAntiAlias(true);
46 | circlePaint.setColor(Color.argb(255,0,0,80));
47 | circlePaint.setStyle(Paint.Style.STROKE);
48 | circlePaint.setStrokeJoin(Paint.Join.MITER);
49 | circlePaint.setStrokeWidth(6f);
50 |
51 | mPaint = new Paint();
52 | mPaint.setAntiAlias(true);
53 | mPaint.setDither(true);
54 | mPaint.setColor(Color.BLACK);
55 | mPaint.setStyle(Paint.Style.STROKE);
56 | mPaint.setStrokeJoin(Paint.Join.ROUND);
57 | mPaint.setStrokeCap(Paint.Cap.ROUND);
58 | mPaint.setStrokeWidth(9);
59 | }
60 |
61 | @Override
62 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
63 | super.onSizeChanged(w, h, oldw, oldh);
64 | try {
65 | mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
66 | mCanvas = new Canvas(mBitmap);
67 | mCanvas.drawColor(Color.WHITE);
68 | } catch(Exception e) {
69 | e.printStackTrace();
70 | }
71 | }
72 |
73 | @Override
74 | protected void onDraw(Canvas canvas) {
75 | canvas.drawColor(Color.WHITE);
76 | super.onDraw(canvas);
77 | canvas.drawBitmap( mBitmap, 0, 0, mBitmapPaint);
78 | canvas.drawPath( mPath, mPaint);
79 | canvas.drawPath( circlePath, circlePaint);
80 | }
81 |
82 | private float mX, mY;
83 | private static final float TOUCH_TOLERANCE = 4;
84 |
85 | private void touch_start(float x, float y) {
86 | mPath.reset();
87 | mPath.moveTo(x, y);
88 | mX = x;
89 | mY = y;
90 | }
91 |
92 | private void touch_move(float x, float y) {
93 | float dx = Math.abs(x - mX);
94 | float dy = Math.abs(y - mY);
95 | if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
96 | mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
97 | mX = x;
98 | mY = y;
99 | circlePath.reset();
100 | circlePath.addCircle(mX, mY, 35, Path.Direction.CW);
101 | }
102 | }
103 |
104 | private void touch_up() {
105 | mPath.lineTo(mX, mY);
106 | circlePath.reset();
107 | // commit the path to our offscreen
108 | mCanvas.drawPath(mPath, mPaint);
109 | // kill this so we don't double draw
110 | mPath.reset();
111 | }
112 |
113 | @Override
114 | public boolean onTouchEvent(MotionEvent event) {
115 | float x = event.getX();
116 | float y = event.getY();
117 | switch(event.getAction()) {
118 | case MotionEvent.ACTION_DOWN:
119 | touch_start(x, y);
120 | invalidate();
121 | break;
122 | case MotionEvent.ACTION_MOVE:
123 | touch_move(x, y);
124 | invalidate();
125 | break;
126 | case MotionEvent.ACTION_UP:
127 | touch_up();
128 | invalidate();
129 | break;
130 | }
131 | return true;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/CustomerAdapter.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.Context;
4 | import android.util.SparseBooleanArray;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.BaseAdapter;
9 | import android.widget.CheckBox;
10 | import android.widget.CompoundButton;
11 | import android.widget.TextView;
12 |
13 | import java.util.ArrayList;
14 | import java.util.List;
15 |
16 | import de.georgsieber.customerdb.model.Customer;
17 |
18 | class CustomerAdapter extends BaseAdapter {
19 |
20 | private Context mContext;
21 | private List mCustomers;
22 | private LayoutInflater mInflater;
23 | private boolean mShowCheckbox = false;
24 |
25 | CustomerAdapter(Context context, List customers) {
26 | mContext = context;
27 | mCustomers = customers;
28 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
29 | }
30 | CustomerAdapter(Context context, List customers, checkedChangedListener listener) {
31 | mContext = context;
32 | mCustomers = customers;
33 | mListener = listener;
34 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
35 | }
36 |
37 | @Override
38 | public int getCount() {
39 | return mCustomers.size();
40 | }
41 |
42 | @Override
43 | public Object getItem(int position) {
44 | return mCustomers.get(position);
45 | }
46 |
47 | @Override
48 | public long getItemId(int position) {
49 | return 0;
50 | }
51 |
52 | @Override
53 | public View getView(int position, View convertView, ViewGroup parent) {
54 | Customer c = mCustomers.get(position);
55 |
56 | if(convertView == null) {
57 | convertView = mInflater.inflate(R.layout.item_list_customer, null);
58 | }
59 | TextView tv1 = (convertView.findViewById(R.id.textViewCustomerListItem1));
60 | TextView tv2 = (convertView.findViewById(R.id.textViewCustomerListItem2));
61 |
62 | tv1.setText(c.getFirstLine());
63 | if(c.getSecondLine().trim().equals("")) {
64 | tv2.setText(mContext.getString(R.string.no_details));
65 | } else {
66 | tv2.setText(c.getSecondLine());
67 | }
68 |
69 | CheckBox currentListItemCheckBox = convertView.findViewById(R.id.checkBoxCustomerListItem);
70 | if(mShowCheckbox) {
71 | currentListItemCheckBox.setTag(position);
72 | currentListItemCheckBox.setChecked(mSparseBooleanArray.get(position));
73 | currentListItemCheckBox.setOnCheckedChangeListener(mCheckedChangeListener);
74 | currentListItemCheckBox.setVisibility(View.VISIBLE);
75 | } else {
76 | currentListItemCheckBox.setVisibility(View.GONE);
77 | }
78 | return convertView;
79 | }
80 |
81 | boolean getShowCheckbox() {
82 | return mShowCheckbox;
83 | }
84 | void setShowCheckbox(boolean visible) {
85 | mShowCheckbox = visible;
86 | notifyDataSetChanged();
87 | }
88 |
89 | private SparseBooleanArray mSparseBooleanArray = new SparseBooleanArray();
90 | private CompoundButton.OnCheckedChangeListener mCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
91 | @Override
92 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
93 | mSparseBooleanArray.put((Integer) buttonView.getTag(), isChecked);
94 | if(mListener != null) mListener.checkedChanged(getCheckedItems());
95 | }
96 | };
97 |
98 | void setAllChecked(boolean checked) {
99 | for(int i=0; i getCheckedItems() {
106 | ArrayList mTempArray = new ArrayList();
107 | for(int i=0;i checked);
118 | }
119 | void setCheckedChangedListener(checkedChangedListener listener) {
120 | this.mListener = listener;
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/tools/CommonDialog.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.tools;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.os.Build;
7 |
8 | import androidx.activity.result.ActivityResultLauncher;
9 | import androidx.appcompat.app.AppCompatActivity;
10 | import androidx.core.content.FileProvider;
11 |
12 | import java.io.File;
13 |
14 | import de.georgsieber.customerdb.R;
15 |
16 | public class CommonDialog {
17 |
18 | public enum TYPE {
19 | OK, WARN, FAIL, NONE
20 | }
21 |
22 | public static void show(final AppCompatActivity context, String title, String text, TYPE icon, final boolean finish) {
23 | if(context == null || context.isFinishing()) return;
24 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
25 | if(context.isDestroyed()) return;
26 | }
27 |
28 | AlertDialog ad = new AlertDialog.Builder(context).create();
29 | ad.setCancelable(!finish);
30 | if(title != null && !title.equals("")) ad.setTitle(title);
31 | if(icon == TYPE.OK) {
32 | if(text != null && (!text.equals(""))) ad.setMessage(text);
33 | ad.setIcon(context.getResources().getDrawable(R.drawable.ic_tick_green_24dp));
34 | } else if(icon == TYPE.WARN) {
35 | if(text != null && (!text.equals(""))) ad.setMessage(text);
36 | ad.setIcon(context.getResources().getDrawable(R.drawable.ic_warning_orange_24dp));
37 | } else if(icon == TYPE.FAIL) {
38 | if(text != null && (!text.equals(""))) ad.setMessage(text);
39 | ad.setIcon(context.getResources().getDrawable(R.drawable.ic_fail_red_36dp));
40 | } else {
41 | ad.setMessage(text);
42 | }
43 | ad.setButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
44 | @Override
45 | public void onClick(DialogInterface dialog, int which) {
46 | dialog.dismiss();
47 | if(finish) context.finish();
48 | }
49 | });
50 | ad.show();
51 | }
52 |
53 | public static void exportFinishedDialog(final AppCompatActivity context, File f, String mimeType, String[] emailRecipients, String emailSubject, String emailText, ActivityResultLauncher resultHandlerExportMoveFile) {
54 | AlertDialog ad = new AlertDialog.Builder(context).create();
55 | ad.setTitle(context.getResources().getString(R.string.export_ok));
56 | ad.setMessage(f.getPath());
57 | ad.setIcon(context.getResources().getDrawable(R.drawable.ic_tick_green_24dp));
58 | ad.setButton(DialogInterface.BUTTON_POSITIVE, context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
59 | @Override
60 | public void onClick(DialogInterface dialog, int which) {
61 | dialog.dismiss();
62 | }
63 | });
64 | ad.setButton(DialogInterface.BUTTON_NEUTRAL, context.getResources().getString(R.string.email), new DialogInterface.OnClickListener() {
65 | @Override
66 | public void onClick(DialogInterface dialog, int which) {
67 | dialog.dismiss();
68 | // this opens app chooser instead of system email app
69 | Intent intent = new Intent(Intent.ACTION_SEND);
70 | intent.setType("message/rfc822");
71 | intent.putExtra(Intent.EXTRA_EMAIL, emailRecipients);
72 | intent.putExtra(Intent.EXTRA_SUBJECT, emailSubject);
73 | intent.putExtra(Intent.EXTRA_TEXT, emailText);
74 | if(f != null) {
75 | intent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, "de.georgsieber.customerdb.provider", f));
76 | }
77 | context.startActivity(Intent.createChooser(intent, context.getResources().getString(R.string.emailtocustomer)));
78 | }
79 | });
80 | if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
81 | ad.setButton(DialogInterface.BUTTON_NEGATIVE, context.getResources().getString(R.string.move), new DialogInterface.OnClickListener() {
82 | @Override
83 | public void onClick(DialogInterface dialog, int which) {
84 | dialog.dismiss();
85 | Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
86 | intent.setType(mimeType);
87 | intent.putExtra(Intent.EXTRA_TITLE, f.getName());
88 | resultHandlerExportMoveFile.launch(intent);
89 | }
90 | });
91 | }
92 | ad.show();
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/TextViewActivity.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.net.Uri;
9 | import com.google.android.material.snackbar.Snackbar;
10 |
11 | import androidx.appcompat.widget.Toolbar;
12 | import androidx.core.content.FileProvider;
13 | import androidx.appcompat.app.AppCompatActivity;
14 | import android.os.Bundle;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.widget.TextView;
19 |
20 | import java.io.File;
21 | import java.io.FileOutputStream;
22 | import java.io.IOException;
23 |
24 | import de.georgsieber.customerdb.tools.ColorControl;
25 |
26 | public class TextViewActivity extends AppCompatActivity {
27 |
28 | String mTitle = "";
29 | String mContent = "";
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | // init settings
34 | SharedPreferences settings = getSharedPreferences(MainActivity.PREFS_NAME, 0);
35 |
36 | // init activity view
37 | super.onCreate(savedInstanceState);
38 | setContentView(R.layout.activity_text_view);
39 |
40 | // init toolbar
41 | Toolbar toolbar = findViewById(R.id.toolbar);
42 | setSupportActionBar(toolbar);
43 | if(getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true);
44 |
45 | // init colors
46 | ColorControl.updateActionBarColor(this, settings);
47 |
48 | // load text
49 | mTitle = getIntent().getStringExtra("title");
50 | if(mTitle != null)
51 | this.setTitle(mTitle);
52 |
53 | mContent = getIntent().getStringExtra("content");
54 | if(mContent != null)
55 | ((TextView) findViewById(R.id.textViewScript)).setText(mContent);
56 | }
57 |
58 | @Override
59 | public boolean onCreateOptionsMenu(Menu menu) {
60 | getMenuInflater().inflate(R.menu.menu_text_view, menu);
61 | return true;
62 | }
63 |
64 | @Override
65 | public boolean onOptionsItemSelected(MenuItem item) {
66 | switch(item.getItemId()) {
67 | case android.R.id.home:
68 | finish();
69 | return true;
70 | case R.id.action_send_via_email:
71 | if(mContent == null) break;
72 | sendViaEmail(mContent);
73 | break;
74 | case R.id.action_copy_to_clipboard:
75 | if(mContent == null) break;
76 | toClipboard(mContent);
77 | break;
78 | }
79 | return super.onOptionsItemSelected(item);
80 | }
81 |
82 | private File getStorageScript() {
83 | File exportDir = new File(getExternalFilesDir(null), "tmp");
84 | exportDir.mkdirs();
85 | return new File(exportDir, "email.txt");
86 | }
87 |
88 | private void scanFile(File f) {
89 | Uri uri = Uri.fromFile(f);
90 | Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
91 | sendBroadcast(scanFileIntent);
92 | }
93 |
94 | public void sendViaEmail(String text) {
95 | File f = getStorageScript();
96 | try {
97 | FileOutputStream stream = new FileOutputStream(f);
98 | stream.write(text.getBytes());
99 | stream.close();
100 | } catch(IOException e) {
101 | e.printStackTrace();
102 | }
103 | scanFile(f);
104 |
105 | Uri attachmentUri = FileProvider.getUriForFile(
106 | this,
107 | "de.georgsieber.customerdb.provider",
108 | f
109 | );
110 | // this opens app chooser instead of system email app
111 | Intent intent = new Intent(Intent.ACTION_SEND);
112 | intent.setType("message/rfc822");
113 | intent.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.app_name));
114 | intent.putExtra(Intent.EXTRA_TEXT, "");
115 | intent.putExtra(Intent.EXTRA_STREAM, attachmentUri);
116 | startActivity(Intent.createChooser(intent, getResources().getString(R.string.email)));
117 | }
118 |
119 | private void toClipboard(String text) {
120 | ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
121 | ClipData clip = ClipData.newPlainText("phone", text);
122 | clipboard.setPrimaryClip(clip);
123 |
124 | Snackbar.make(findViewById(R.id.linearLayoutScriptActivityMainView), getResources().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG)
125 | .setAction("Action", null)
126 | .show();
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
27 |
35 |
45 |
46 |
54 |
64 |
65 |
73 |
83 |
84 |
85 |
88 |
89 |
95 |
96 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/importexport/CustomerCsvBuilder.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb.importexport;
2 |
3 | import android.util.Log;
4 |
5 | import com.opencsv.CSVReader;
6 | import com.opencsv.CSVWriter;
7 |
8 | import java.io.File;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.io.InputStreamReader;
12 | import java.io.StringWriter;
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | import de.georgsieber.customerdb.CustomerDatabase;
18 | import de.georgsieber.customerdb.model.CustomField;
19 | import de.georgsieber.customerdb.model.Customer;
20 |
21 | public class CustomerCsvBuilder {
22 |
23 | private List mCustomers = new ArrayList<>();
24 | private List mAllCustomFields;
25 |
26 | public CustomerCsvBuilder(List _customers, List _allCustomFields) {
27 | mCustomers = _customers;
28 | mAllCustomFields = _allCustomFields;
29 | }
30 | public CustomerCsvBuilder(Customer _customer, List _allCustomFields) {
31 | mCustomers.add(_customer);
32 | mAllCustomFields = _allCustomFields;
33 | }
34 |
35 | private String buildCsvContent() {
36 | StringWriter content = new StringWriter();
37 |
38 | CSVWriter csvWriter = new CSVWriter(content);
39 |
40 | List headers = new ArrayList<>(Arrays.asList(
41 | "id", "title", "first_name", "last_name",
42 | "phone_home", "phone_mobile", "phone_work", "email",
43 | "street", "zipcode", "city", "country", "customer_group",
44 | "newsletter", "birthday", "last_modified", "notes"
45 | ));
46 | for(CustomField cf : mAllCustomFields) {
47 | //Log.e("CSV", "add cf: "+cf.mTitle);
48 | headers.add(cf.mTitle);
49 | }
50 | csvWriter.writeNext(headers.toArray(new String[0]));
51 |
52 | for(Customer currentCustomer : mCustomers) {
53 | List values = new ArrayList<>(Arrays.asList(
54 | Long.toString(currentCustomer.mId),
55 | currentCustomer.mTitle,
56 | currentCustomer.mFirstName,
57 | currentCustomer.mLastName,
58 | currentCustomer.mPhoneHome,
59 | currentCustomer.mPhoneMobile,
60 | currentCustomer.mPhoneWork,
61 | currentCustomer.mEmail,
62 | currentCustomer.mStreet,
63 | currentCustomer.mZipcode,
64 | currentCustomer.mCity,
65 | currentCustomer.mCountry,
66 | currentCustomer.mCustomerGroup,
67 | currentCustomer.mNewsletter ? "1" : "0",
68 | currentCustomer.mBirthday==null ? "" : CustomerDatabase.dateToStringRaw(currentCustomer.mBirthday),
69 | currentCustomer.mLastModified==null ? "" : CustomerDatabase.dateToString(currentCustomer.mLastModified),
70 | currentCustomer.mNotes
71 | ));
72 | for(CustomField cf : mAllCustomFields) {
73 | values.add(currentCustomer.getCustomField(cf.mTitle));
74 | }
75 | csvWriter.writeNext(values.toArray(new String[0]));
76 | }
77 |
78 | return content.toString();
79 | }
80 |
81 | public boolean saveCsvFile(File f) {
82 | try {
83 | FileOutputStream stream = new FileOutputStream(f);
84 | stream.write(buildCsvContent().getBytes());
85 | stream.close();
86 | return true;
87 | } catch (IOException e) {
88 | Log.e("Exception", "File write failed: " + e.toString());
89 | }
90 | return false;
91 | }
92 |
93 | public static List readCsvFile(InputStreamReader isr) throws Exception {
94 | List newCustomers = new ArrayList<>();
95 | CSVReader reader = new CSVReader(isr);
96 | String[] headers = new String[]{};
97 | String[] line;
98 | int counter = 0;
99 | while((line = reader.readNext()) != null) {
100 | try {
101 | if(counter == 0) {
102 | headers = line;
103 | } else {
104 | Customer newCustomer = new Customer();
105 | int counter2 = 0;
106 | for(String field : line) {
107 | //Log.e("CSV", headers[counter2] + " -> "+ field);
108 | newCustomer.putCustomerAttribute(headers[counter2], field);
109 | counter2 ++;
110 | }
111 | newCustomers.add(newCustomer);
112 | }
113 | } catch(Exception ignored) {}
114 | counter ++;
115 | }
116 | return newCustomers;
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/de/georgsieber/customerdb/VoucherAdapter.java:
--------------------------------------------------------------------------------
1 | package de.georgsieber.customerdb;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.util.SparseBooleanArray;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.BaseAdapter;
10 | import android.widget.CheckBox;
11 | import android.widget.CompoundButton;
12 | import android.widget.TextView;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import de.georgsieber.customerdb.model.Voucher;
18 |
19 | public class VoucherAdapter extends BaseAdapter {
20 |
21 | private Context mContext;
22 | private List mVouchers;
23 | private String mCurrency;
24 | private LayoutInflater mInflater;
25 | private boolean mShowCheckbox = false;
26 |
27 | VoucherAdapter(Context context, List vouchers, String currency) {
28 | mContext = context;
29 | mVouchers = vouchers;
30 | mCurrency = currency;
31 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
32 | }
33 | VoucherAdapter(Context context, List vouchers, String currency, checkedChangedListener listener) {
34 | mContext = context;
35 | mVouchers = vouchers;
36 | mCurrency = currency;
37 | mListener = listener;
38 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
39 | }
40 |
41 | @Override
42 | public int getCount() {
43 | return mVouchers.size();
44 | }
45 |
46 | @Override
47 | public Object getItem(int position) {
48 | return mVouchers.get(position);
49 | }
50 |
51 | @Override
52 | public long getItemId(int position) {
53 | return 0;
54 | }
55 |
56 | @SuppressLint("SetTextI18n")
57 | @Override
58 | public View getView(int position, View convertView, ViewGroup parent) {
59 | Voucher v = mVouchers.get(position);
60 |
61 | if(convertView == null) {
62 | convertView = mInflater.inflate(R.layout.item_list_customer, null);
63 | }
64 | TextView tv1 = (convertView.findViewById(R.id.textViewCustomerListItem1));
65 | TextView tv2 = (convertView.findViewById(R.id.textViewCustomerListItem2));
66 |
67 | if(v.mCurrentValue != v.mOriginalValue)
68 | tv1.setText(v.getCurrentValueString()+" "+mCurrency + " ("+v.getOriginalValueString()+" "+mCurrency+")");
69 | else
70 | tv1.setText(v.getCurrentValueString()+" "+mCurrency);
71 |
72 | tv2.setText(v.mVoucherNo.equals("") ? v.getIdString() : v.mVoucherNo);
73 |
74 | CheckBox currentListItemCheckBox = convertView.findViewById(R.id.checkBoxCustomerListItem);
75 | if(mShowCheckbox) {
76 | currentListItemCheckBox.setTag(position);
77 | currentListItemCheckBox.setChecked(mSparseBooleanArray.get(position));
78 | currentListItemCheckBox.setOnCheckedChangeListener(mCheckedChangeListener);
79 | currentListItemCheckBox.setVisibility(View.VISIBLE);
80 | } else {
81 | currentListItemCheckBox.setVisibility(View.GONE);
82 | }
83 |
84 | return convertView;
85 | }
86 |
87 | boolean getShowCheckbox() {
88 | return mShowCheckbox;
89 | }
90 | void setShowCheckbox(boolean visible) {
91 | mShowCheckbox = visible;
92 | notifyDataSetChanged();
93 | }
94 |
95 | private SparseBooleanArray mSparseBooleanArray = new SparseBooleanArray();
96 | private CompoundButton.OnCheckedChangeListener mCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
97 | @Override
98 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
99 | mSparseBooleanArray.put((Integer) buttonView.getTag(), isChecked);
100 | if(mListener != null) mListener.checkedChanged(getCheckedItems());
101 | }
102 | };
103 |
104 | void setAllChecked(boolean checked) {
105 | for(int i = 0; i< mVouchers.size(); i++) {
106 | mSparseBooleanArray.put(i, checked);
107 | }
108 | if(mListener != null) mListener.checkedChanged(getCheckedItems());
109 | }
110 |
111 | ArrayList getCheckedItems() {
112 | ArrayList mTempArray = new ArrayList<>();
113 | for(int i = 0; i< mVouchers.size(); i++) {
114 | if(mSparseBooleanArray.get(i)) {
115 | mTempArray.add(mVouchers.get(i));
116 | }
117 | }
118 | return mTempArray;
119 | }
120 |
121 | private checkedChangedListener mListener = null;
122 | public interface checkedChangedListener {
123 | void checkedChanged(ArrayList checked);
124 | }
125 | void setCheckedChangedListener(checkedChangedListener listener) {
126 | this.mListener = listener;
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_import_export_voucher.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
22 |
23 |
28 |
29 |
32 |
33 |
38 |
39 |
42 |
43 |
47 |
51 |
54 |
55 |
60 |
61 |
62 |
65 |
66 |
72 |
73 |
76 |
80 |
83 |
84 |
90 |
95 |
96 |
99 |
103 |
106 |
107 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------