diff --git a/OpenPGP-Keychain/AndroidManifest.xml b/OpenPGP-Keychain/AndroidManifest.xml index 8027715af..da655ba02 100644 --- a/OpenPGP-Keychain/AndroidManifest.xml +++ b/OpenPGP-Keychain/AndroidManifest.xml @@ -1,24 +1,8 @@ - @@ -104,8 +82,7 @@ android:name=".ui.KeyListSecretActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_manage_secret_keys" - android:launchMode="singleTop" - android:uiOptions="splitActionBarWhenNarrow" > + android:launchMode="singleTop" > @@ -120,8 +97,16 @@ android:name=".ui.EditKeyActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_edit_key" - android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="stateHidden" /> + + + + + + + + + + + @@ -329,22 +323,6 @@ - - - - - - - - - - - - diff --git a/OpenPGP-Keychain/assets/fontawesome-webfont.ttf b/OpenPGP-Keychain/assets/fontawesome-webfont.ttf new file mode 100644 index 000000000..7ec2e1de8 Binary files /dev/null and b/OpenPGP-Keychain/assets/fontawesome-webfont.ttf differ diff --git a/OpenPGP-Keychain/build.gradle b/OpenPGP-Keychain/build.gradle index 80c0f05cd..0fffd1fd3 100644 --- a/OpenPGP-Keychain/build.gradle +++ b/OpenPGP-Keychain/build.gradle @@ -4,6 +4,7 @@ buildscript { } dependencies { + // NOTE: Avoid using dynamic versions (+). This breaks offline builds! classpath 'com.android.tools.build:gradle:0.6.3' } } @@ -19,10 +20,12 @@ repositories { */ dependencies { compile fileTree(dir: 'libs', includes: ['*.jar'], excludes: ['android-support-v4.jar']) - compile 'com.android.support:support-v4:18.0.+' // already in actionbarsherlock + compile 'com.android.support:support-v4:19.0.+' // already in actionbarsherlock compile project(':libraries:ActionBarSherlock') compile project(':libraries:HtmlTextView') - compile project(':libraries:pinned-section-listview:library') + compile project(':libraries:StickyListHeaders:library') + compile project(':libraries:zxing') + compile project(':libraries:AndroidBootstrap') } android { diff --git a/OpenPGP-Keychain/libs/android-support-v4.jar b/OpenPGP-Keychain/libs/android-support-v4.jar index 99e063b33..9056828a0 100644 Binary files a/OpenPGP-Keychain/libs/android-support-v4.jar and b/OpenPGP-Keychain/libs/android-support-v4.jar differ diff --git a/OpenPGP-Keychain/project.properties b/OpenPGP-Keychain/project.properties index 7347abfcd..76caac668 100644 --- a/OpenPGP-Keychain/project.properties +++ b/OpenPGP-Keychain/project.properties @@ -11,4 +11,6 @@ target=android-19 android.library.reference.1=../libraries/ActionBarSherlock android.library.reference.2=../libraries/HtmlTextView -android.library.reference.3=../libraries/pinned-section-listview/library +android.library.reference.3=../libraries/StickyListHeaders/library +android.library.reference.4=../libraries/zxing +android.library.reference.5=../libraries/AndroidBootstrap diff --git a/OpenPGP-Keychain/res/drawable-finger/btn_circle.xml b/OpenPGP-Keychain/res/drawable-finger/btn_circle.xml deleted file mode 100644 index 6c3c7fc1a..000000000 --- a/OpenPGP-Keychain/res/drawable-finger/btn_circle.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable.png deleted file mode 100644 index ae063b545..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable_focused.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable_focused.png deleted file mode 100644 index 7a5d4fe4e..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_disable_focused.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_normal.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_normal.png deleted file mode 100644 index 5eda66883..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_normal.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_pressed.png deleted file mode 100644 index 88848bac2..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_selected.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_selected.png deleted file mode 100644 index 74690705f..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/btn_circle_selected.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_minus.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_minus.png deleted file mode 100644 index 27af3faf4..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_minus.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_plus.png b/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_plus.png deleted file mode 100644 index b24168c32..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi-finger/ic_btn_round_plus.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_default.png deleted file mode 100644 index 0d51bcb68..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_pressed.png deleted file mode 100644 index d4cc0f8ea..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_decrypt_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_default.png deleted file mode 100644 index 07617bb9d..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_pressed.png deleted file mode 100644 index b8fe6e1d6..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_encrypt_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_default.png deleted file mode 100644 index 233fddffc..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_pressed.png deleted file mode 100644 index dad8694f8..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_help_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_default.png deleted file mode 100644 index 7be4837a0..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_pressed.png deleted file mode 100644 index a4fe3c903..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_import_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_default.png deleted file mode 100644 index de83398c2..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_pressed.png deleted file mode 100644 index a86bc1bf9..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_manage_keys_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_default.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_default.png deleted file mode 100644 index f8b54961e..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_default.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_pressed.png b/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_pressed.png deleted file mode 100644 index 6a5c92138..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/dashboard_my_keys_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/drawer_shadow.9.png b/OpenPGP-Keychain/res/drawable-hdpi/drawer_shadow.9.png new file mode 100644 index 000000000..224cc4ff4 Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-hdpi/drawer_shadow.9.png differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/ic_drawer.png b/OpenPGP-Keychain/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 000000000..ff7b1def9 Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-hdpi/ic_drawer.png differ diff --git a/OpenPGP-Keychain/res/drawable-hdpi/ic_menu_filebrowser.png b/OpenPGP-Keychain/res/drawable-hdpi/ic_menu_filebrowser.png deleted file mode 100644 index 3db304fa8..000000000 Binary files a/OpenPGP-Keychain/res/drawable-hdpi/ic_menu_filebrowser.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable.png deleted file mode 100644 index 33b74a66c..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable_focused.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable_focused.png deleted file mode 100644 index 005ad8dca..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_disable_focused.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_normal.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_normal.png deleted file mode 100644 index fc5af1c9f..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_normal.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_pressed.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_pressed.png deleted file mode 100644 index 8f40afdfc..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_selected.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_selected.png deleted file mode 100644 index c74fac227..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/btn_circle_selected.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_minus.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_minus.png deleted file mode 100644 index 96dbb17d2..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_minus.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_plus.png b/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_plus.png deleted file mode 100644 index 1ec8a956a..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi-finger/ic_btn_round_plus.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi/drawer_shadow.9.png b/OpenPGP-Keychain/res/drawable-mdpi/drawer_shadow.9.png new file mode 100644 index 000000000..3797f99c0 Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-mdpi/drawer_shadow.9.png differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi/ic_drawer.png b/OpenPGP-Keychain/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 000000000..fb681ba26 Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-mdpi/ic_drawer.png differ diff --git a/OpenPGP-Keychain/res/drawable-mdpi/ic_menu_filebrowser.png b/OpenPGP-Keychain/res/drawable-mdpi/ic_menu_filebrowser.png deleted file mode 100644 index fda13f1be..000000000 Binary files a/OpenPGP-Keychain/res/drawable-mdpi/ic_menu_filebrowser.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable-xhdpi/drawer_shadow.9.png b/OpenPGP-Keychain/res/drawable-xhdpi/drawer_shadow.9.png new file mode 100644 index 000000000..fa3d853e9 Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-xhdpi/drawer_shadow.9.png differ diff --git a/OpenPGP-Keychain/res/drawable-xhdpi/ic_drawer.png b/OpenPGP-Keychain/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 000000000..b9bc3d70f Binary files /dev/null and b/OpenPGP-Keychain/res/drawable-xhdpi/ic_drawer.png differ diff --git a/OpenPGP-Keychain/res/drawable-xhdpi/ic_menu_filebrowser.png b/OpenPGP-Keychain/res/drawable-xhdpi/ic_menu_filebrowser.png deleted file mode 100644 index d1324014d..000000000 Binary files a/OpenPGP-Keychain/res/drawable-xhdpi/ic_menu_filebrowser.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/btn_circle_disable.png b/OpenPGP-Keychain/res/drawable/btn_circle_disable.png deleted file mode 100644 index 33b74a66c..000000000 Binary files a/OpenPGP-Keychain/res/drawable/btn_circle_disable.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/btn_circle_disable_focused.png b/OpenPGP-Keychain/res/drawable/btn_circle_disable_focused.png deleted file mode 100644 index 005ad8dca..000000000 Binary files a/OpenPGP-Keychain/res/drawable/btn_circle_disable_focused.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/btn_circle_normal.png b/OpenPGP-Keychain/res/drawable/btn_circle_normal.png deleted file mode 100644 index fc5af1c9f..000000000 Binary files a/OpenPGP-Keychain/res/drawable/btn_circle_normal.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/btn_circle_pressed.png b/OpenPGP-Keychain/res/drawable/btn_circle_pressed.png deleted file mode 100644 index 8f40afdfc..000000000 Binary files a/OpenPGP-Keychain/res/drawable/btn_circle_pressed.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/btn_circle_selected.png b/OpenPGP-Keychain/res/drawable/btn_circle_selected.png deleted file mode 100644 index c74fac227..000000000 Binary files a/OpenPGP-Keychain/res/drawable/btn_circle_selected.png and /dev/null differ diff --git a/OpenPGP-Keychain/res/drawable/dashboard_decrypt.xml b/OpenPGP-Keychain/res/drawable/dashboard_decrypt.xml deleted file mode 100644 index 981e38a0b..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_decrypt.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable/dashboard_encrypt.xml b/OpenPGP-Keychain/res/drawable/dashboard_encrypt.xml deleted file mode 100644 index af812dc51..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_encrypt.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable/dashboard_help.xml b/OpenPGP-Keychain/res/drawable/dashboard_help.xml deleted file mode 100644 index e121ea0d1..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_help.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable/dashboard_import.xml b/OpenPGP-Keychain/res/drawable/dashboard_import.xml deleted file mode 100644 index e5857dc6c..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_import.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable/dashboard_manage_keys.xml b/OpenPGP-Keychain/res/drawable/dashboard_manage_keys.xml deleted file mode 100644 index ebc519253..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_manage_keys.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/drawable/dashboard_my_keys.xml b/OpenPGP-Keychain/res/drawable/dashboard_my_keys.xml deleted file mode 100644 index d4045db45..000000000 --- a/OpenPGP-Keychain/res/drawable/dashboard_my_keys.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml index a40444e0f..a88d7afd2 100644 --- a/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml +++ b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml @@ -1,5 +1,6 @@ @@ -41,12 +42,16 @@ android:layout_height="wrap_content" android:orientation="horizontal" > - + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/sign_key_layout.xml b/OpenPGP-Keychain/res/layout/sign_key_layout.xml deleted file mode 100644 index 4530831ee..000000000 --- a/OpenPGP-Keychain/res/layout/sign_key_layout.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/OpenPGP-Keychain/res/layout/view_key_activity.xml b/OpenPGP-Keychain/res/layout/view_key_activity.xml new file mode 100644 index 000000000..babec70f6 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/view_key_activity.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/view_key_keys_item.xml b/OpenPGP-Keychain/res/layout/view_key_keys_item.xml new file mode 100644 index 000000000..b50253980 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/view_key_keys_item.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/view_key_userids_item.xml b/OpenPGP-Keychain/res/layout/view_key_userids_item.xml new file mode 100644 index 000000000..2d022ba13 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/view_key_userids_item.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_edit.xml b/OpenPGP-Keychain/res/menu/key_edit.xml new file mode 100644 index 000000000..38c52e7f0 --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_edit.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_list_public.xml b/OpenPGP-Keychain/res/menu/key_list_public.xml new file mode 100644 index 000000000..7b6261558 --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_list_public.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_list_public_multi.xml b/OpenPGP-Keychain/res/menu/key_list_public_multi.xml new file mode 100644 index 000000000..92481e9cb --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_list_public_multi.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_list_secret.xml b/OpenPGP-Keychain/res/menu/key_list_secret.xml new file mode 100644 index 000000000..c610eda35 --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_list_secret.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_list_secret_multi.xml b/OpenPGP-Keychain/res/menu/key_list_secret_multi.xml new file mode 100644 index 000000000..a10d1e2fa --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_list_secret_multi.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/key_view.xml b/OpenPGP-Keychain/res/menu/key_view.xml new file mode 100644 index 000000000..1078cd937 --- /dev/null +++ b/OpenPGP-Keychain/res/menu/key_view.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/nfc_beam.xml b/OpenPGP-Keychain/res/menu/nfc_beam.xml deleted file mode 100644 index e1c088b86..000000000 --- a/OpenPGP-Keychain/res/menu/nfc_beam.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/OpenPGP-Keychain/res/raw/help_about.html b/OpenPGP-Keychain/res/raw/help_about.html index 0ed0d3b05..54af42b16 100644 --- a/OpenPGP-Keychain/res/raw/help_about.html +++ b/OpenPGP-Keychain/res/raw/help_about.html @@ -12,13 +12,15 @@ And don't add newlines before or after p tags because of transifex -->

Developers OpenPGP Keychain

  • Dominik Schürmann (Lead developer)
  • -
  • Ash Hughes
  • +
  • Ash Hughes (crypto patches)
  • Brian C. Barnes
  • +
  • Bahtiar 'kalkin' Gadimov (UI)
  • +

Developers APG 1.x

    -
  • Thialfihar (Lead developer)
  • -
  • Senecaso (QRCode, sign key, upload key)
  • +
  • 'Thialfihar' (Lead developer)
  • +
  • 'Senecaso' (QRCode, sign key, upload key)
  • Oliver Runge
  • Markus Doits
@@ -26,7 +28,9 @@ And don't add newlines before or after p tags because of transifex -->

Libraries

  • ActionBarSherlock (Apache License v2)
  • -
  • ZXing QRCode Integration (Apache License v2)
  • +
  • StickyListHeaders (Apache License v2)
  • +
  • Android-Bootstrap (MIT License)
  • +
  • ZXing (Apache License v2)
  • SpongyCastle (MIT X11 License)
  • HtmlTextView (Apache License v2)
  • Icons from RRZE Icon Set (Creative Commons Attribution Share-Alike licence 3.0)
  • diff --git a/OpenPGP-Keychain/res/values-sw600dp/dimens.xml b/OpenPGP-Keychain/res/values-sw600dp/dimens.xml new file mode 100644 index 000000000..1ba777d65 --- /dev/null +++ b/OpenPGP-Keychain/res/values-sw600dp/dimens.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values-sw720dp-land/dimens.xml b/OpenPGP-Keychain/res/values-sw720dp-land/dimens.xml new file mode 100644 index 000000000..eee741a51 --- /dev/null +++ b/OpenPGP-Keychain/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,9 @@ + + + + 128dp + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/colors.xml b/OpenPGP-Keychain/res/values/colors.xml index d1dc6c1f4..780137181 100644 --- a/OpenPGP-Keychain/res/values/colors.xml +++ b/OpenPGP-Keychain/res/values/colors.xml @@ -3,5 +3,5 @@ #31b6e7 #cecbce - + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/dimens.xml b/OpenPGP-Keychain/res/values/dimens.xml new file mode 100644 index 000000000..a6dd14032 --- /dev/null +++ b/OpenPGP-Keychain/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + + 16dp + 16dp + + \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/strings.xml b/OpenPGP-Keychain/res/values/strings.xml index 807daaeb4..87f7c5a8f 100644 --- a/OpenPGP-Keychain/res/values/strings.xml +++ b/OpenPGP-Keychain/res/values/strings.xml @@ -19,8 +19,8 @@ - Manage Public Keys - Manage Secret Keys + Contacts + Secret Keys Select Public Key Select Secret Key Encrypt @@ -44,6 +44,7 @@ Export to Key Server Unknown Signature Key Sign Key + Key Details Help Share key with NFC @@ -53,6 +54,9 @@ General Defaults Advanced + Master Key + Master User ID + Actions Sign (Clipboard) @@ -78,9 +82,11 @@ Settings + Help Registered Apps Import from file Import from QR Code + Import Import from NFC Export all keys Export to file @@ -95,8 +101,11 @@ Share with… Share with QR Code Share with NFC + Copy to clipboard Sign key Beam settings + Cancel + Encrypt to… Sign @@ -129,6 +138,7 @@ Comment Email Send Key to Server? + Fingerprint Select 1 Selected Selected @@ -142,6 +152,8 @@ %s key server(s) Fingerprint: Secret Key: + not valid + Secret Keyring None @@ -190,6 +202,7 @@ Please specify which file to export to.\nWARNING! File will be overwritten if it exists. Please specify which file to export to.\nWARNING! You are about to export SECRET keys.\nWARNING! File will be overwritten if it exists. Do you really want to delete the key \'%s\'?\nYou can\'t undo this! + Do you really want to delete all selected keys?\nYou can\'t undo this! Do you really want to delete the SECRET key \'%s\'?\nYou can\'t undo this! Successfully added %1$s key(s) and updated %2$s key(s). Successfully added %s key(s). @@ -206,7 +219,6 @@ Unknown key %s, do you want to try finding it on a keyserver? Successfully sent key to server Successfully signed key - Long press one entry in this list to show more options! This list is empty! Successfully sent key with NFC Beam! @@ -292,7 +304,7 @@ very slow - Manage Public Keys + Contacts My Secret Keys Encrypt Decrypt @@ -322,8 +334,8 @@ No registered applications! - Show advanced settings… - Hide advanced settings… + Show advanced settings + Hide advanced settings No key selected Select key Save @@ -343,5 +355,31 @@ Go through all QR Codes using \'Next\', and scan them one by one. QR Code %1$d of %2$d + Share with NFC + + + + 1 key selected. + %d keys selected. + + + No keys available yet… + You can start by + or + creating your own key pair + importing keys. + + + Encrypt to this contact + + + Contacts + Encrypt + Decrypt + Import Keys + My Keys + Registered Apps + Open navigation drawer + Close navigation drawer \ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/styles.xml b/OpenPGP-Keychain/res/values/styles.xml index ef3ff63f6..15214fa62 100644 --- a/OpenPGP-Keychain/res/values/styles.xml +++ b/OpenPGP-Keychain/res/values/styles.xml @@ -18,16 +18,6 @@ - - - - diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java index e9b0b67d4..fb7851774 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java @@ -27,18 +27,7 @@ import org.spongycastle.bcpg.CompressionAlgorithmTags; */ public final class Id { - public static final String TAG = "APG"; - public static final class menu { - public static final int export = 0x21070001; - public static final int delete = 0x21070002; - public static final int edit = 0x21070003; - public static final int update = 0x21070004; - public static final int exportToServer = 0x21070005; - public static final int share = 0x21070006; - public static final int share_qr_code = 0x21070007; - public static final int share_nfc = 0x21070008; - public static final int signKey = 0x21070009; public static final class option { public static final int new_pass_phrase = 0x21070001; @@ -82,20 +71,6 @@ public final class Id { public static final int unknown_signature_key = 0x00006011; } - // public static final class message { - // public static final int progress_update = 0x21070001; - // public static final int done = 0x21070002; - // public static final int import_keys = 0x21070003; - // public static final int export_keys = 0x21070004; - // public static final int import_done = 0x21070005; - // public static final int export_done = 0x21070006; - // public static final int create_key = 0x21070007; - // public static final int edit_key = 0x21070008; - // public static final int delete_done = 0x21070009; - // public static final int query_done = 0x21070010; - // public static final int unknown_signature_key = 0x21070011; - // } - // use only lower 16 bits due to compatibility lib public static final class request { public static final int public_keys = 0x00007001; @@ -109,18 +84,6 @@ public final class Id { public static final int sign_key = 0x00007009; } - // public static final class request { - // public static final int public_keys = 0x21070001; - // public static final int secret_keys = 0x21070002; - // public static final int filename = 0x21070003; - // public static final int output_filename = 0x21070004; - // public static final int key_server_preference = 0x21070005; - // public static final int look_up_key_id = 0x21070006; - // public static final int export_to_server = 0x21070007; - // public static final int import_from_qr_code = 0x21070008; - // public static final int sign_key = 0x21070009; - // } - public static final class dialog { public static final int pass_phrase = 0x21070001; public static final int encrypting = 0x21070002; @@ -136,7 +99,6 @@ public final class Id { public static final int export_keys = 0x2107000c; public static final int exporting = 0x2107000d; public static final int new_account = 0x2107000e; - // public static final int about = 0x2107000f; public static final int change_log = 0x21070010; public static final int output_filename = 0x21070011; public static final int delete_file = 0x21070012; @@ -152,11 +114,6 @@ public final class Id { public static final int export_keys = 0x21070002; } - // public static final class database { - // public static final int type_public = 0; - // public static final int type_secret = 1; - // } - public static final class type { public static final int public_key = 0x21070001; public static final int secret_key = 0x21070002; diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/ExportHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/ExportHelper.java new file mode 100644 index 000000000..261e26be6 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/ExportHelper.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.helper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +public class ExportHelper { + protected FileDialogFragment mFileDialog; + protected String mExportFilename; + + SherlockFragmentActivity activity; + + public ExportHelper(SherlockFragmentActivity activity) { + super(); + this.activity = activity; + } + + public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) { + long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(deleteHandler); + + DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, + new long[] { keyRingRowId }, keyType); + + deleteKeyDialog.show(activity.getSupportFragmentManager(), "deleteKeyDialog"); + } + + /** + * Show dialog where to export keys + * + * @param keyRingMasterKeyId + * if -1 export all keys + */ + public void showExportKeysDialog(final Uri dataUri, final int keyType, + final String exportFilename) { + mExportFilename = exportFilename; + + // Message is received after file is selected + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == FileDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + + long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); + + // TODO? + long keyRingMasterKeyId = ProviderHelper.getSecretMasterKeyId(activity, + keyRingRowId); + + exportKeys(keyRingMasterKeyId, keyType); + } + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + String title = null; + if (dataUri != null) { + // single key export + title = activity.getString(R.string.title_export_key); + } else { + title = activity.getString(R.string.title_export_keys); + } + + String message = null; + if (keyType == Id.type.public_key) { + message = activity.getString(R.string.specify_file_to_export_to); + } else { + message = activity.getString(R.string.specify_file_to_export_secret_keys_to); + } + + mFileDialog = FileDialogFragment.newInstance(messenger, title, message, + exportFilename, null, Id.request.filename); + + mFileDialog.show(activity.getSupportFragmentManager(), "fileDialog"); + } + }); + } + + /** + * Export keys + * + * @param keyRingMasterKeyId + * if -1 export all keys + */ + public void exportKeys(long keyRingMasterKeyId, int keyType) { + Log.d(Constants.TAG, "exportKeys started"); + + // Send all information needed to service to export key in other thread + Intent intent = new Intent(activity, KeychainIntentService.class); + + intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING); + + // fill values for this action + Bundle data = new Bundle(); + + data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); + data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType); + + if (keyRingMasterKeyId == -1) { + data.putBoolean(KeychainIntentService.EXPORT_ALL, true); + } else { + data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId); + } + + intent.putExtra(KeychainIntentService.EXTRA_DATA, data); + + // Message is received after exporting is done in ApgService + KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(activity, + R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { + public void handleMessage(Message message) { + // handle messages by standard ApgHandler first + super.handleMessage(message); + + if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { + // get returned data bundle + Bundle returnData = message.getData(); + + int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT); + String toastMessage; + if (exported == 1) { + toastMessage = activity.getString(R.string.key_exported); + } else if (exported > 0) { + toastMessage = activity.getString(R.string.keys_exported, exported); + } else { + toastMessage = activity.getString(R.string.no_keys_exported); + } + Toast.makeText(activity, toastMessage, Toast.LENGTH_SHORT).show(); + + } + }; + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(exportHandler); + intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); + + // show progress dialog + exportHandler.showProgressDialog(activity); + + // start service with intent + activity.startService(intent); + } + + public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == Id.request.filename) { + if (resultCode == Activity.RESULT_OK && data != null) { + try { + String path = data.getData().getPath(); + Log.d(Constants.TAG, "path=" + path); + + // set filename used in export/import dialogs + mFileDialog.setFilename(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); + } + } + return true; + } + + return false; + } +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java index 8fa2df1f5..9f3cd8e88 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -79,23 +79,4 @@ public class OtherHelper { } } - /** - * Splits userId string into naming part and email part - * - * @param userId - * @return array with naming (0) and email (1) - */ - public static String[] splitUserId(String userId) { - String[] output = new String[2]; - - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - output[1] = "<" + chunks[1]; - } - output[0] = userId; - - return output; - } - } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java index e2d89bfab..edb30496a 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/pgp/PgpKeyHelper.java @@ -22,6 +22,8 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.openpgp.PGPPublicKey; @@ -40,6 +42,17 @@ import android.content.Context; public class PgpKeyHelper { + /** + * Returns the last 9 chars of a fingerprint + * + * @param fingerprint + * String containing short or long fingerprint + * @return + */ + public static String shortifyFingerprint(String fingerprint) { + return fingerprint.substring(41); + } + public static Date getCreationDate(PGPPublicKey key) { return key.getCreationTime(); } @@ -513,4 +526,32 @@ public class PgpKeyHelper { return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); } + /** + * Splits userId string into naming part, email part, and comment part + * + * @param userId + * @return array with naming (0), email (1), comment (2) + */ + public static String[] splitUserId(String userId) { + String[] result = new String[] { "", "", "" }; + + Pattern withComment = Pattern.compile("^(.*) \\((.*)\\) <(.*)>$"); + Matcher matcher = withComment.matcher(userId); + if (matcher.matches()) { + result[0] = matcher.group(1); + result[1] = matcher.group(3); + result[2] = matcher.group(2); + return result; + } + + Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); + matcher = withoutComment.matcher(userId); + if (matcher.matches()) { + result[0] = matcher.group(1); + result[1] = matcher.group(2); + return result; + } + return result; + } + } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java index 82bb473f6..d2381f6f0 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -69,8 +69,8 @@ public class KeychainContract { public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider"; - private static final Uri BASE_CONTENT_URI_INTERNAL = Uri.parse("content://" - + CONTENT_AUTHORITY); + private static final Uri BASE_CONTENT_URI_INTERNAL = Uri + .parse("content://" + CONTENT_AUTHORITY); public static final String BASE_KEY_RINGS = "key_rings"; public static final String BASE_DATA = "data"; @@ -185,6 +185,14 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) .appendPath(PATH_KEYS).appendPath(keyRowId).build(); } + + public static Uri buildKeysUri(Uri keyRingUri) { + return keyRingUri.buildUpon().appendPath(PATH_KEYS).build(); + } + + public static Uri buildKeysUri(Uri keyRingUri, String keyRowId) { + return keyRingUri.buildUpon().appendPath(PATH_KEYS).appendPath(keyRowId).build(); + } } public static class UserIds implements UserIdsColumns, BaseColumns { @@ -216,6 +224,14 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId) .appendPath(PATH_USER_IDS).appendPath(userIdRowId).build(); } + + public static Uri buildUserIdsUri(Uri keyRingUri) { + return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).build(); + } + + public static Uri buildUserIdsUri(Uri keyRingUri, String userIdRowId) { + return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).appendPath(userIdRowId).build(); + } } public static class ApiApps implements ApiAppsColumns, BaseColumns { diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java index f12048277..1683c7c0e 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -60,7 +60,7 @@ public class ProviderHelper { * @param queryUri * @return */ - private static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) { + public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) { Cursor cursor = context.getContentResolver().query(queryUri, new String[] { KeyRings._ID, KeyRings.KEY_RING_DATA }, null, null, null); @@ -493,7 +493,7 @@ public class ProviderHelper { */ public static long getPublicMasterKeyId(Context context, long keyRingRowId) { Uri queryUri = KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyId(context, queryUri, keyRingRowId); + return getMasterKeyId(context, queryUri); } /** @@ -551,7 +551,7 @@ public class ProviderHelper { */ public static long getSecretMasterKeyId(Context context, long keyRingRowId) { Uri queryUri = KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowId)); - return getMasterKeyId(context, queryUri, keyRingRowId); + return getMasterKeyId(context, queryUri); } /** @@ -562,7 +562,7 @@ public class ProviderHelper { * @param keyRingRowId * @return */ - private static long getMasterKeyId(Context context, Uri queryUri, long keyRingRowId) { + public static long getMasterKeyId(Context context, Uri queryUri) { String[] projection = new String[] { KeyRings.MASTER_KEY_ID }; ContentResolver cr = context.getContentResolver(); @@ -592,7 +592,7 @@ public class ProviderHelper { return getKeyRingsAsArmoredString(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds); } - private static ArrayList getKeyRingsAsArmoredString(Context context, Uri uri, + public static ArrayList getKeyRingsAsArmoredString(Context context, Uri uri, long[] masterKeyIds) { ArrayList output = new ArrayList(); @@ -661,7 +661,7 @@ public class ProviderHelper { return getKeyRingsAsByteArray(context, KeyRings.buildSecretKeyRingsUri(), masterKeyIds); } - private static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) { + public static byte[] getKeyRingsAsByteArray(Context context, Uri uri, long[] masterKeyIds) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); if (masterKeyIds != null && masterKeyIds.length > 0) { diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java index e592f5d57..bb6e427a4 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/AppSettingsFragment.java @@ -49,12 +49,13 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; +import com.beardedhen.androidbootstrap.BootstrapButton; + public class AppSettingsFragment extends Fragment { // model @@ -62,12 +63,12 @@ public class AppSettingsFragment extends Fragment { // view private LinearLayout mAdvancedSettingsContainer; - private Button mAdvancedSettingsButton; + private BootstrapButton mAdvancedSettingsButton; private TextView mAppNameView; private ImageView mAppIconView; private TextView mKeyUserId; private TextView mKeyUserIdRest; - private Button mSelectKeyButton; + private BootstrapButton mSelectKeyButton; private Spinner mEncryptionAlgorithm; private Spinner mHashAlgorithm; private Spinner mCompression; @@ -116,7 +117,8 @@ public class AppSettingsFragment extends Fragment { } private void initView(View view) { - mAdvancedSettingsButton = (Button) view.findViewById(R.id.api_app_settings_advanced_button); + mAdvancedSettingsButton = (BootstrapButton) view + .findViewById(R.id.api_app_settings_advanced_button); mAdvancedSettingsContainer = (LinearLayout) view .findViewById(R.id.api_app_settings_advanced); @@ -124,7 +126,8 @@ public class AppSettingsFragment extends Fragment { mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon); mKeyUserId = (TextView) view.findViewById(R.id.api_app_settings_user_id); mKeyUserIdRest = (TextView) view.findViewById(R.id.api_app_settings_user_id_rest); - mSelectKeyButton = (Button) view.findViewById(R.id.api_app_settings_select_key_button); + mSelectKeyButton = (BootstrapButton) view + .findViewById(R.id.api_app_settings_select_key_button); mEncryptionAlgorithm = (Spinner) view .findViewById(R.id.api_app_settings_encryption_algorithm); mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm); @@ -204,11 +207,13 @@ public class AppSettingsFragment extends Fragment { if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) { mAdvancedSettingsContainer.startAnimation(invisibleAnimation); mAdvancedSettingsContainer.setVisibility(View.GONE); - mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced); + mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced)); + mAdvancedSettingsButton.setLeftIcon("fa-caret-up"); } else { mAdvancedSettingsContainer.startAnimation(visibleAnimation); mAdvancedSettingsContainer.setVisibility(View.VISIBLE); - mAdvancedSettingsButton.setText(R.string.api_settings_hide_advanced); + mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced)); + mAdvancedSettingsButton.setLeftIcon("fa-caret-down"); } } }); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java index 4530ac2fc..3c553fff5 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListActivity.java @@ -18,44 +18,19 @@ package org.sufficientlysecure.keychain.service.remote; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.MainActivity; +import org.sufficientlysecure.keychain.ui.DrawerActivity; -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; import android.os.Bundle; -public class RegisteredAppsListActivity extends SherlockFragmentActivity { - private ActionBar mActionBar; +public class RegisteredAppsListActivity extends DrawerActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mActionBar = getSupportActionBar(); - setContentView(R.layout.api_apps_list_activity); - mActionBar.setDisplayShowTitleEnabled(true); - mActionBar.setDisplayHomeAsUpEnabled(true); + setupDrawerNavigation(savedInstanceState); } - /** - * Menu Options - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - default: - return super.onOptionsItemSelected(item); - } - } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java index 1b504a374..4c9d553ad 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/remote/RegisteredAppsListFragment.java @@ -41,9 +41,6 @@ public class RegisteredAppsListFragment extends SherlockListFragment implements // This is the Adapter being used to display the list's data. RegisteredAppsAdapter mAdapter; - // If non-null, this is the current filter the user has provided. - String mCurFilter; - @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -75,8 +72,7 @@ public class RegisteredAppsListFragment extends SherlockListFragment implements } // These are the Contacts rows that we will retrieve. - static final String[] CONSUMERS_SUMMARY_PROJECTION = new String[] { ApiApps._ID, - ApiApps.PACKAGE_NAME }; + static final String[] PROJECTION = new String[] { ApiApps._ID, ApiApps.PACKAGE_NAME }; public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This @@ -87,7 +83,7 @@ public class RegisteredAppsListFragment extends SherlockListFragment implements // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, CONSUMERS_SUMMARY_PROJECTION, null, null, + return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC"); } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 78ad4c9be..6cc0b3b5a 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -60,19 +60,18 @@ import android.view.View.OnClickListener; import android.view.animation.AnimationUtils; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewFlipper; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.BootstrapButton; @SuppressLint("NewApi") -public class DecryptActivity extends SherlockFragmentActivity { +public class DecryptActivity extends DrawerActivity { /* Intents */ // without permission @@ -107,7 +106,7 @@ public class DecryptActivity extends SherlockFragmentActivity { private EditText mFilename = null; private CheckBox mDeleteAfter = null; - private ImageButton mBrowse = null; + private BootstrapButton mBrowse = null; private String mInputFilename = null; private String mOutputFilename = null; @@ -144,13 +143,6 @@ public class DecryptActivity extends SherlockFragmentActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - case Id.menu.option.decrypt: { decryptClicked(); @@ -216,7 +208,7 @@ public class DecryptActivity extends SherlockFragmentActivity { mMessage.setMinimumHeight(height); mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (ImageButton) findViewById(R.id.btn_browse); + mBrowse = (BootstrapButton) findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*", @@ -238,13 +230,15 @@ public class DecryptActivity extends SherlockFragmentActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.decrypt); + setContentView(R.layout.decrypt_activity); // set actionbar without home button if called from another app ActionBarHelper.setBackButton(this); initView(); + setupDrawerNavigation(savedInstanceState); + // Handle intent actions handleActions(getIntent()); @@ -262,7 +256,8 @@ public class DecryptActivity extends SherlockFragmentActivity { if (matcher.matches()) { data = matcher.group(1); mMessage.setText(data); - Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.using_clipboard_content, Toast.LENGTH_SHORT) + .show(); } } } @@ -472,8 +467,9 @@ public class DecryptActivity extends SherlockFragmentActivity { if (!file.exists() || !file.isFile()) { Toast.makeText( this, - getString(R.string.error_message, getString(R.string.error_file_not_found)), - Toast.LENGTH_SHORT).show(); + getString(R.string.error_message, + getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT) + .show(); return; } } @@ -592,7 +588,8 @@ public class DecryptActivity extends SherlockFragmentActivity { } mSecretKeyId = Id.key.symmetric; if (!PgpOperation.hasSymmetricEncryption(this, inStream)) { - throw new PgpGeneralException(getString(R.string.error_no_known_encryption_found)); + throw new PgpGeneralException( + getString(R.string.error_no_known_encryption_found)); } mAssumeSymmetricEncryption = true; } @@ -790,8 +787,8 @@ public class DecryptActivity extends SherlockFragmentActivity { .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN)) { mSignatureStatusImage.setImageResource(R.drawable.overlay_error); Toast.makeText(DecryptActivity.this, - R.string.unknown_signature_key_touch_to_look_up, Toast.LENGTH_LONG) - .show(); + R.string.unknown_signature_key_touch_to_look_up, + Toast.LENGTH_LONG).show(); } else { mSignatureStatusImage.setImageResource(R.drawable.overlay_error); } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DrawerActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DrawerActivity.java new file mode 100644 index 000000000..ee8a01432 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/DrawerActivity.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v4.app.ActionBarDrawerToggle; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.view.ActionProvider; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.FontAwesomeText; + +/** + * some fundamental ideas from https://github.com/tobykurien/SherlockNavigationDrawer + * + * + */ +public class DrawerActivity extends SherlockFragmentActivity { + private DrawerLayout mDrawerLayout; + private ListView mDrawerList; + private ActionBarDrawerToggle mDrawerToggle; + + private CharSequence mDrawerTitle; + private CharSequence mTitle; + + private static Class[] mItemsClass = new Class[] { KeyListPublicActivity.class, + EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class, + KeyListSecretActivity.class, RegisteredAppsListActivity.class }; + + private static final int MENU_ID_PREFERENCE = 222; + private static final int MENU_ID_HELP = 223; + + protected void setupDrawerNavigation(Bundle savedInstanceState) { + mDrawerTitle = getString(R.string.app_name); + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + mDrawerList = (ListView) findViewById(R.id.left_drawer); + + // set a custom shadow that overlays the main content when the drawer + // opens + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + NavItem mItemIconTexts[] = new NavItem[] { + new NavItem("fa-user", getString(R.string.nav_contacts)), + new NavItem("fa-lock", getString(R.string.nav_encrypt)), + new NavItem("fa-unlock", getString(R.string.nav_decrypt)), + new NavItem("fa-download", getString(R.string.nav_import)), + new NavItem("fa-key", getString(R.string.nav_secret_keys)), + new NavItem("fa-android", getString(R.string.nav_apps)) }; + + mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item, + mItemIconTexts)); + + mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); + + // enable ActionBar app icon to behave as action to toggle nav drawer + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + + // ActionBarDrawerToggle ties together the the proper interactions + // between the sliding drawer and the action bar app icon + mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ + mDrawerLayout, /* DrawerLayout object */ + R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ + R.string.drawer_open, /* "open drawer" description for accessibility */ + R.string.drawer_close /* "close drawer" description for accessibility */ + ) { + public void onDrawerClosed(View view) { + getSupportActionBar().setTitle(mTitle); + // creates call to onPrepareOptionsMenu() + supportInvalidateOptionsMenu(); + } + + public void onDrawerOpened(View drawerView) { + mTitle = getSupportActionBar().getTitle(); + getSupportActionBar().setTitle(mDrawerTitle); + // creates call to onPrepareOptionsMenu() + supportInvalidateOptionsMenu(); + } + }; + mDrawerLayout.setDrawerListener(mDrawerToggle); + + // if (savedInstanceState == null) { + // selectItem(0); + // } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences); + menu.add(42, MENU_ID_HELP, 101, R.string.menu_help); + + return super.onCreateOptionsMenu(menu); + } + + /* Called whenever we call invalidateOptionsMenu() */ + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // If the nav drawer is open, hide action items related to the content + // view + boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); + // menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + // The action bar home/up action should open or close the drawer. + // ActionBarDrawerToggle will take care of this. + if (mDrawerToggle.onOptionsItemSelected(getMenuItem(item))) { + return true; + } + + switch (item.getItemId()) { + case MENU_ID_PREFERENCE: { + Intent intent = new Intent(this, PreferencesActivity.class); + startActivity(intent); + return true; + } + case MENU_ID_HELP: { + Intent intent = new Intent(this, HelpActivity.class); + startActivity(intent); + return true; + } + default: + return super.onOptionsItemSelected(item); + } + + // Handle action buttons + // switch (item.getItemId()) { + // case R.id.action_websearch: + // // create intent to perform web search for this planet + // Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + // intent.putExtra(SearchManager.QUERY, getSupportActionBar().getTitle()); + // // catch event that there's no activity to handle intent + // if (intent.resolveActivity(getPackageManager()) != null) { + // startActivity(intent); + // } else { + // Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show(); + // } + // return true; + // default: + // return super.onOptionsItemSelected(item); + // } + } + + private android.view.MenuItem getMenuItem(final MenuItem item) { + return new android.view.MenuItem() { + @Override + public int getItemId() { + return item.getItemId(); + } + + public boolean isEnabled() { + return true; + } + + @Override + public boolean collapseActionView() { + return false; + } + + @Override + public boolean expandActionView() { + return false; + } + + @Override + public ActionProvider getActionProvider() { + return null; + } + + @Override + public View getActionView() { + return null; + } + + @Override + public char getAlphabeticShortcut() { + return 0; + } + + @Override + public int getGroupId() { + return 0; + } + + @Override + public Drawable getIcon() { + return null; + } + + @Override + public Intent getIntent() { + return null; + } + + @Override + public ContextMenuInfo getMenuInfo() { + return null; + } + + @Override + public char getNumericShortcut() { + return 0; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public SubMenu getSubMenu() { + return null; + } + + @Override + public CharSequence getTitle() { + return null; + } + + @Override + public CharSequence getTitleCondensed() { + return null; + } + + @Override + public boolean hasSubMenu() { + return false; + } + + @Override + public boolean isActionViewExpanded() { + return false; + } + + @Override + public boolean isCheckable() { + return false; + } + + @Override + public boolean isChecked() { + return false; + } + + @Override + public boolean isVisible() { + return false; + } + + @Override + public android.view.MenuItem setActionProvider(ActionProvider actionProvider) { + return null; + } + + @Override + public android.view.MenuItem setActionView(View view) { + return null; + } + + @Override + public android.view.MenuItem setActionView(int resId) { + return null; + } + + @Override + public android.view.MenuItem setAlphabeticShortcut(char alphaChar) { + return null; + } + + @Override + public android.view.MenuItem setCheckable(boolean checkable) { + return null; + } + + @Override + public android.view.MenuItem setChecked(boolean checked) { + return null; + } + + @Override + public android.view.MenuItem setEnabled(boolean enabled) { + return null; + } + + @Override + public android.view.MenuItem setIcon(Drawable icon) { + return null; + } + + @Override + public android.view.MenuItem setIcon(int iconRes) { + return null; + } + + @Override + public android.view.MenuItem setIntent(Intent intent) { + return null; + } + + @Override + public android.view.MenuItem setNumericShortcut(char numericChar) { + return null; + } + + @Override + public android.view.MenuItem setOnActionExpandListener(OnActionExpandListener listener) { + return null; + } + + @Override + public android.view.MenuItem setOnMenuItemClickListener( + OnMenuItemClickListener menuItemClickListener) { + return null; + } + + @Override + public android.view.MenuItem setShortcut(char numericChar, char alphaChar) { + return null; + } + + @Override + public void setShowAsAction(int actionEnum) { + } + + @Override + public android.view.MenuItem setShowAsActionFlags(int actionEnum) { + return null; + } + + @Override + public android.view.MenuItem setTitle(CharSequence title) { + return null; + } + + @Override + public android.view.MenuItem setTitle(int title) { + return null; + } + + @Override + public android.view.MenuItem setTitleCondensed(CharSequence title) { + return null; + } + + @Override + public android.view.MenuItem setVisible(boolean visible) { + return null; + } + }; + } + + /* The click listener for ListView in the navigation drawer */ + private class DrawerItemClickListener implements ListView.OnItemClickListener { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + selectItem(position); + } + } + + private void selectItem(int position) { + // update selected item and title, then close the drawer + mDrawerList.setItemChecked(position, true); + // setTitle(mDrawerTitles[position]); + mDrawerLayout.closeDrawer(mDrawerList); + + finish(); + overridePendingTransition(0, 0); + + Intent intent = new Intent(this, mItemsClass[position]); + startActivity(intent); + // disable animation of activity start + overridePendingTransition(0, 0); + } + + /** + * When using the ActionBarDrawerToggle, you must call it during onPostCreate() and + * onConfigurationChanged()... + */ + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. + mDrawerToggle.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Pass any configuration change to the drawer toggles + mDrawerToggle.onConfigurationChanged(newConfig); + } + + private class NavItem { + public String icon; + public String title; + + public NavItem(String icon, String title) { + super(); + this.icon = icon; + this.title = title; + } + } + + private class NavigationDrawerAdapter extends ArrayAdapter { + Context context; + int layoutResourceId; + NavItem data[] = null; + + public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) { + super(context, layoutResourceId, data); + this.layoutResourceId = layoutResourceId; + this.context = context; + this.data = data; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + NavItemHolder holder = null; + + if (row == null) { + LayoutInflater inflater = ((Activity) context).getLayoutInflater(); + row = inflater.inflate(layoutResourceId, parent, false); + + holder = new NavItemHolder(); + holder.img = (FontAwesomeText) row.findViewById(R.id.drawer_item_icon); + holder.txtTitle = (TextView) row.findViewById(R.id.drawer_item_text); + + row.setTag(holder); + } else { + holder = (NavItemHolder) row.getTag(); + } + + NavItem item = data[position]; + holder.txtTitle.setText(item.title); + holder.img.setIcon(item.icon); + + return row; + } + + } + + static class NavItemHolder { + FontAwesomeText img; + TextView txtTitle; + } + +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index 7abee78f3..be2e4115b 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann + * Copyright (C) 2012-2013 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.helper.ExportHelper; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.widget.KeyEditor; @@ -45,6 +47,7 @@ import org.sufficientlysecure.keychain.util.Log; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -53,7 +56,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -61,6 +63,9 @@ import android.widget.LinearLayout; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.BootstrapButton; public class EditKeyActivity extends SherlockFragmentActivity { @@ -72,13 +77,14 @@ public class EditKeyActivity extends SherlockFragmentActivity { public static final String EXTRA_USER_IDS = "user_ids"; public static final String EXTRA_NO_PASSPHRASE = "no_passphrase"; public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys"; - public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; - public static final String EXTRA_MASTER_CAN_SIGN = "master_can_sign"; // results when saving key public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id"; public static final String RESULT_EXTRA_USER_ID = "user_id"; + // EDIT + private Uri mDataUri; + private PGPSecretKeyRing mKeyRing = null; private SectionView mUserIdsView; @@ -87,7 +93,7 @@ public class EditKeyActivity extends SherlockFragmentActivity { private String mCurrentPassPhrase = null; private String mNewPassPhrase = null; - private Button mChangePassPhrase; + private BootstrapButton mChangePassPhrase; private CheckBox mNoPassphrase; @@ -96,34 +102,13 @@ public class EditKeyActivity extends SherlockFragmentActivity { Vector mKeysUsages; boolean masterCanSign = true; - // will be set to false to build layout later in handler - private boolean mBuildLayout = true; + ExportHelper mExportHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Inflate a "Done"/"Cancel" custom action bar - ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - // save - saveClicked(); - } - }, R.string.btn_do_not_save, new View.OnClickListener() { - @Override - public void onClick(View v) { - // cancel - cancelClicked(); - } - }); - - setContentView(R.layout.edit_key); - - // find views - mChangePassPhrase = (Button) findViewById(R.id.edit_key_btn_change_pass_phrase); - mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); + mExportHelper = new ExportHelper(this); mUserIds = new Vector(); mKeys = new Vector(); @@ -137,32 +122,6 @@ public class EditKeyActivity extends SherlockFragmentActivity { } else if (ACTION_EDIT_KEY.equals(action)) { handleActionEditKey(intent); } - - mChangePassPhrase.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - showSetPassphraseDialog(); - } - }); - - // disable passphrase when no passphrase checkobox is checked! - mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - // remove passphrase - mNewPassPhrase = null; - - mChangePassPhrase.setVisibility(View.GONE); - } else { - mChangePassPhrase.setVisibility(View.VISIBLE); - } - } - }); - - if (mBuildLayout) { - buildLayout(); - } } /** @@ -171,6 +130,20 @@ public class EditKeyActivity extends SherlockFragmentActivity { * @param intent */ private void handleActionCreateKey(Intent intent) { + // Inflate a "Done"/"Cancel" custom action bar + ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + saveClicked(); + } + }, R.string.btn_do_not_save, new View.OnClickListener() { + @Override + public void onClick(View v) { + cancelClicked(); + } + }); + Bundle extras = intent.getExtras(); mCurrentPassPhrase = ""; @@ -197,9 +170,6 @@ public class EditKeyActivity extends SherlockFragmentActivity { boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS); if (generateDefaultKeys) { - // build layout in handler after generating keys not directly in onCreate - mBuildLayout = false; - // Send all information needed to service generate keys in other thread Intent serviceIntent = new Intent(this, KeychainIntentService.class); serviceIntent.setAction(KeychainIntentService.ACTION_GENERATE_DEFAULT_RSA_KEYS); @@ -256,12 +226,55 @@ public class EditKeyActivity extends SherlockFragmentActivity { startService(serviceIntent); } } + } else { + buildLayout(); + } + } + + /** + * Handle intent action to edit existing key + * + * @param intent + */ + private void handleActionEditKey(Intent intent) { + // Inflate a "Done"/"Cancel" custom action bar + ActionBarHelper.setDoneView(getSupportActionBar(), R.string.btn_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + saveClicked(); + } + }); + + mDataUri = intent.getData(); + if (mDataUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mDataUri); + + long keyRingRowId = Long.valueOf(mDataUri.getLastPathSegment()); + + // get master key id using row id + long masterKeyId = ProviderHelper.getSecretMasterKeyId(this, keyRingRowId); + + boolean masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(this, keyRingRowId); + + String passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); + if (passphrase == null) { + showPassphraseDialog(masterKeyId, masterCanSign); + } else { + // PgpMain.setEditPassPhrase(passPhrase); + mCurrentPassPhrase = passphrase; + + finallyEdit(masterKeyId, masterCanSign); + } } } private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) { // Message is received after passphrase is cached - final boolean mCanSign = masterCanSign; Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { @@ -291,51 +304,54 @@ public class EditKeyActivity extends SherlockFragmentActivity { } } - /** - * Handle intent action to edit existing key - * - * @param intent - */ - @SuppressWarnings("unchecked") - private void handleActionEditKey(Intent intent) { - Bundle extras = intent.getExtras(); - - if (extras != null) { - if (extras.containsKey(EXTRA_MASTER_CAN_SIGN)) { - masterCanSign = extras.getBoolean(EXTRA_MASTER_CAN_SIGN); - } - if (extras.containsKey(EXTRA_MASTER_KEY_ID)) { - long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID); - - // build layout in edit() - mBuildLayout = false; - - String passPhrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId); - if (passPhrase == null) { - showPassphraseDialog(masterKeyId, masterCanSign); - } else { - // PgpMain.setEditPassPhrase(passPhrase); - mCurrentPassPhrase = passPhrase; - - finallyEdit(masterKeyId, masterCanSign); - } - - } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // show menu only on edit + if (mDataUri != null) { + return super.onPrepareOptionsMenu(menu); + } else { + return false; } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getSupportMenuInflater().inflate(R.menu.key_edit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_key_edit_cancel: + cancelClicked(); + return true; + case R.id.menu_key_edit_export_file: + mExportHelper.showExportKeysDialog(mDataUri, Id.type.secret_key, Constants.path.APP_DIR + + "/secexport.asc"); + return true; + case R.id.menu_key_edit_delete: { + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + setResult(RESULT_CANCELED); + finish(); + } + } + }; + + mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("unchecked") private void finallyEdit(final long masterKeyId, final boolean masterCanSign) { - // TODO: ??? - if (mCurrentPassPhrase == null) { - mCurrentPassPhrase = ""; - } - - if (mCurrentPassPhrase.equals("")) { - // check "no passphrase" checkbox and remove button - mNoPassphrase.setChecked(true); - mChangePassPhrase.setVisibility(View.GONE); - } - if (masterKeyId != 0) { PGPSecretKey masterKey = null; mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId); @@ -357,7 +373,18 @@ public class EditKeyActivity extends SherlockFragmentActivity { } } + // TODO: ??? + if (mCurrentPassPhrase == null) { + mCurrentPassPhrase = ""; + } + buildLayout(); + + if (mCurrentPassPhrase.equals("")) { + // check "no passphrase" checkbox and remove button + mNoPassphrase.setChecked(true); + mChangePassPhrase.setVisibility(View.GONE); + } } /** @@ -402,6 +429,12 @@ public class EditKeyActivity extends SherlockFragmentActivity { * id and key. */ private void buildLayout() { + setContentView(R.layout.edit_key_activity); + + // find views + mChangePassPhrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_pass_phrase); + mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); + // Build layout based on given userIds and keys LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -418,6 +451,28 @@ public class EditKeyActivity extends SherlockFragmentActivity { container.addView(mKeysView); updatePassPhraseButtonText(); + + mChangePassPhrase.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + showSetPassphraseDialog(); + } + }); + + // disable passphrase when no passphrase checkobox is checked! + mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + // remove passphrase + mNewPassPhrase = null; + + mChangePassPhrase.setVisibility(View.GONE); + } else { + mChangePassPhrase.setVisibility(View.VISIBLE); + } + } + }); } private long getMasterKeyId() { @@ -604,7 +659,14 @@ public class EditKeyActivity extends SherlockFragmentActivity { } private void updatePassPhraseButtonText() { - mChangePassPhrase.setText(isPassphraseSet() ? R.string.btn_change_passphrase - : R.string.btn_set_passphrase); + mChangePassPhrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase) + : getString(R.string.btn_set_passphrase)); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!mExportHelper.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EncryptActivity.java index 2dfdf254e..c974dfd46 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EncryptActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/EncryptActivity.java @@ -55,21 +55,19 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.animation.AnimationUtils; import android.widget.ArrayAdapter; -import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewFlipper; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.BootstrapButton; -public class EncryptActivity extends SherlockFragmentActivity { +public class EncryptActivity extends DrawerActivity { /* Intents */ public static final String ACTION_ENCRYPT = Constants.INTENT_PREFIX + "ENCRYPT"; @@ -87,7 +85,7 @@ public class EncryptActivity extends SherlockFragmentActivity { private long mEncryptionKeyIds[] = null; private EditText mMessage = null; - private Button mSelectKeysButton = null; + private BootstrapButton mSelectKeysButton = null; private boolean mEncryptEnabled = false; private String mEncryptString = ""; @@ -117,7 +115,7 @@ public class EncryptActivity extends SherlockFragmentActivity { private EditText mFilename = null; private CheckBox mDeleteAfter = null; - private ImageButton mBrowse = null; + private BootstrapButton mBrowse = null; private String mInputFilename = null; private String mOutputFilename = null; @@ -154,13 +152,6 @@ public class EncryptActivity extends SherlockFragmentActivity { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - case Id.menu.option.encrypt_to_clipboard: encryptToClipboardClicked(); @@ -181,13 +172,15 @@ public class EncryptActivity extends SherlockFragmentActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.encrypt); + setContentView(R.layout.encrypt_activity); // set actionbar without home button if called from another app ActionBarHelper.setBackButton(this); initView(); + setupDrawerNavigation(savedInstanceState); + // Handle intent actions handleActions(getIntent()); @@ -492,8 +485,9 @@ public class EncryptActivity extends SherlockFragmentActivity { if (!file.exists() || !file.isFile()) { Toast.makeText( this, - getString(R.string.error_message, getString(R.string.error_file_not_found)), - Toast.LENGTH_SHORT).show(); + getString(R.string.error_message, + getString(R.string.error_file_not_found)), Toast.LENGTH_SHORT) + .show(); return; } } @@ -511,7 +505,8 @@ public class EncryptActivity extends SherlockFragmentActivity { gotPassPhrase = (passPhrase.length() != 0); if (!gotPassPhrase) { - Toast.makeText(this, R.string.passphrase_must_not_be_empty, Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.passphrase_must_not_be_empty, Toast.LENGTH_SHORT) + .show(); return; } } else { @@ -523,8 +518,8 @@ public class EncryptActivity extends SherlockFragmentActivity { } if (!encryptIt && mSecretKeyId == 0) { - Toast.makeText(this, R.string.select_encryption_or_signature_key, Toast.LENGTH_SHORT) - .show(); + Toast.makeText(this, R.string.select_encryption_or_signature_key, + Toast.LENGTH_SHORT).show(); return; } @@ -825,7 +820,7 @@ public class EncryptActivity extends SherlockFragmentActivity { mModeLabel.setOnClickListener(nextModeClickListener); mMessage = (EditText) findViewById(R.id.message); - mSelectKeysButton = (Button) findViewById(R.id.btn_selectEncryptKeys); + mSelectKeysButton = (BootstrapButton) findViewById(R.id.btn_selectEncryptKeys); mSign = (CheckBox) findViewById(R.id.sign); mMainUserId = (TextView) findViewById(R.id.mainUserId); mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); @@ -841,7 +836,7 @@ public class EncryptActivity extends SherlockFragmentActivity { mMessage.setMinimumHeight(height); mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (ImageButton) findViewById(R.id.btn_browse); + mBrowse = (BootstrapButton) findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { FileHelper.openFile(EncryptActivity.this, mFilename.getText().toString(), "*/*", @@ -853,10 +848,12 @@ public class EncryptActivity extends SherlockFragmentActivity { Choice[] choices = new Choice[] { new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zip, "ZIP (" + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.compression_fast) + ")"), - new Choice(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.compression_very_slow) - + ")"), }; + new Choice(Id.choice.compression.zip, "ZIP (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.zlib, "ZLIB (" + + getString(R.string.compression_fast) + ")"), + new Choice(Id.choice.compression.bzip2, "BZIP2 (" + + getString(R.string.compression_very_slow) + ")"), }; ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, choices); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -896,9 +893,9 @@ public class EncryptActivity extends SherlockFragmentActivity { private void updateView() { if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - mSelectKeysButton.setText(R.string.no_keys_selected); + mSelectKeysButton.setText(getString(R.string.no_keys_selected)); } else if (mEncryptionKeyIds.length == 1) { - mSelectKeysButton.setText(R.string.one_key_selected); + mSelectKeysButton.setText(getString(R.string.one_key_selected)); } else { mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " " + getResources().getString(R.string.n_keys_selected)); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpActivity.java index 13350b6c6..d604c1c86 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpActivity.java @@ -17,24 +17,21 @@ package org.sufficientlysecure.keychain.ui; +import java.util.ArrayList; + import org.sufficientlysecure.keychain.R; +import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.ViewPager; import android.widget.TextView; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar.Tab; -import com.actionbarsherlock.view.MenuItem; - -import java.util.ArrayList; - -import android.content.Context; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; - import com.actionbarsherlock.app.SherlockFragmentActivity; public class HelpActivity extends SherlockFragmentActivity { @@ -45,37 +42,19 @@ public class HelpActivity extends SherlockFragmentActivity { TextView tabCenter; TextView tabText; - /** - * Menu Items - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + setContentView(R.layout.help_activity); - mViewPager = new ViewPager(this); - mViewPager.setId(R.id.pager); + mViewPager = (ViewPager) findViewById(R.id.pager); - setContentView(mViewPager); - ActionBar bar = getSupportActionBar(); - bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - bar.setDisplayShowTitleEnabled(true); - bar.setDisplayHomeAsUpEnabled(true); + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); mTabsAdapter = new TabsAdapter(this, mViewPager); @@ -87,20 +66,20 @@ public class HelpActivity extends SherlockFragmentActivity { Bundle startBundle = new Bundle(); startBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_start); - mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_start)), + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)), HelpFragmentHtml.class, startBundle, (selectedTab == 0 ? true : false)); Bundle nfcBundle = new Bundle(); nfcBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_nfc_beam); - mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_nfc_beam)), + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)), HelpFragmentHtml.class, nfcBundle, (selectedTab == 1 ? true : false)); Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpFragmentHtml.ARG_HTML_FILE, R.raw.help_changelog); - mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_changelog)), + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)), HelpFragmentHtml.class, changelogBundle, (selectedTab == 2 ? true : false)); - mTabsAdapter.addTab(bar.newTab().setText(getString(R.string.help_tab_about)), + mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)), HelpFragmentAbout.class, null, (selectedTab == 3 ? true : false)); } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java index e7a977707..840ebb650 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/HelpFragmentAbout.java @@ -48,7 +48,7 @@ public class HelpFragmentAbout extends SherlockFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.help_fragment_about, container, false); + View view = inflater.inflate(R.layout.help_about_fragment, container, false); TextView versionText = (TextView) view.findViewById(R.id.help_about_version); versionText.setText(getString(R.string.help_about_version) + " " + getVersion()); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index 37edc8f49..7d8f4154f 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -22,7 +22,6 @@ import java.util.List; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry; @@ -30,26 +29,30 @@ import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; import org.sufficientlysecure.keychain.util.Log; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.view.View; +import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Toast; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.BootstrapButton; -public class ImportKeysActivity extends SherlockFragmentActivity implements OnNavigationListener { +public class ImportKeysActivity extends DrawerActivity implements OnNavigationListener { public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY"; public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_QR_CODE"; @@ -73,14 +76,36 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa OnNavigationListener mOnNavigationListener; String[] mNavigationStrings; + BootstrapButton mImportButton; + BootstrapButton mImportSignUploadButton; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.import_keys); + setContentView(R.layout.import_keys_activity); + + mImportButton = (BootstrapButton) findViewById(R.id.import_import); + mImportButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + importKeys(); + } + }); + mImportSignUploadButton = (BootstrapButton) findViewById(R.id.import_sign_and_upload); + mImportSignUploadButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + signAndUploadOnClick(); + } + }); + + getSupportActionBar().setDisplayShowTitleEnabled(false); + + setupDrawerNavigation(savedInstanceState); // set actionbar without home button if called from another app - ActionBarHelper.setBackButton(this); + // ActionBarHelper.setBackButton(this); // set drop down navigation mNavigationStrings = getResources().getStringArray(R.array.import_action_list); @@ -90,7 +115,6 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa list.setDropDownViewResource(R.layout.sherlock_spinner_dropdown_item); getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); getSupportActionBar().setListNavigationCallbacks(list, this); - getSupportActionBar().setDisplayShowTitleEnabled(false); handleActions(savedInstanceState, getIntent()); } @@ -216,23 +240,6 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa mListFragment.loadNew(importData, importFilename); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - - default: - return super.onOptionsItemSelected(item); - - } - } - // private void importAndSignOld(final long keyId, final String expectedFingerprint) { // if (expectedFingerprint != null && expectedFingerprint.length() > 0) { // @@ -345,7 +352,8 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD); String toastMessage; if (added > 0 && updated > 0) { - toastMessage = getString(R.string.keys_added_and_updated, added, updated); + toastMessage = getString(R.string.keys_added_and_updated, added, + updated); } else if (added > 0) { toastMessage = getString(R.string.keys_added, added); } else if (updated > 0) { @@ -396,11 +404,11 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa } } - public void importOnClick(View view) { + public void importOnClick() { importKeys(); } - public void signAndUploadOnClick(View view) { + public void signAndUploadOnClick() { // first, import! // importOnClick(view); @@ -409,4 +417,43 @@ public class ImportKeysActivity extends SherlockFragmentActivity implements OnNa .show(); } + /** + * NFC + */ + @Override + public void onResume() { + super.onResume(); + // Check to see that the Activity started due to an Android Beam + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { + handleActionNdefDiscovered(getIntent()); + } + } + + /** + * NFC + */ + @Override + public void onNewIntent(Intent intent) { + // onResume gets called after this to handle the intent + setIntent(intent); + } + + /** + * NFC: Parses the NDEF Message from the intent and prints to the TextView + */ + @SuppressLint("NewApi") + void handleActionNdefDiscovered(Intent intent) { + Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + // only one message sent during the beam + NdefMessage msg = (NdefMessage) rawMsgs[0]; + // record 0 contains the MIME type, record 1 is the AAR, if present + byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + + Intent importIntent = new Intent(this, ImportKeysActivity.class); + importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY); + importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes); + + handleActions(null, importIntent); + } + } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java index dcb7dbcc6..31f758395 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java @@ -26,12 +26,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; + +import com.beardedhen.androidbootstrap.BootstrapButton; public class ImportKeysClipboardFragment extends Fragment { private ImportKeysActivity mImportActivity; - private Button mButton; + private BootstrapButton mButton; /** * Creates new instance of this fragment @@ -52,7 +53,7 @@ public class ImportKeysClipboardFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_clipboard_fragment, container, false); - mButton = (Button) view.findViewById(R.id.import_clipboard_button); + mButton = (BootstrapButton) view.findViewById(R.id.import_clipboard_button); mButton.setOnClickListener(new OnClickListener() { @Override diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index fbca9013b..ea76d2898 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -31,14 +31,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; -import android.widget.ImageButton; + +import com.beardedhen.androidbootstrap.BootstrapButton; public class ImportKeysFileFragment extends Fragment { public static final String ARG_PATH = "path"; private ImportKeysActivity mImportActivity; private EditText mFilename; - private ImageButton mBrowse; + private BootstrapButton mBrowse; /** * Creates new instance of this fragment @@ -61,7 +62,7 @@ public class ImportKeysFileFragment extends Fragment { View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false); mFilename = (EditText) view.findViewById(R.id.import_keys_file_input); - mBrowse = (ImageButton) view.findViewById(R.id.import_keys_file_browse); + mBrowse = (BootstrapButton) view.findViewById(R.id.import_keys_file_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java index 2d756dde6..83af8cf48 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java @@ -26,11 +26,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; + +import com.beardedhen.androidbootstrap.BootstrapButton; public class ImportKeysNFCFragment extends Fragment { - private Button mButton; + private BootstrapButton mButton; /** * Creates new instance of this fragment @@ -51,7 +52,7 @@ public class ImportKeysNFCFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_nfc_fragment, container, false); - mButton = (Button) view.findViewById(R.id.import_nfc_button); + mButton = (BootstrapButton) view.findViewById(R.id.import_nfc_button); mButton.setOnClickListener(new OnClickListener() { @Override diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index 62b59b4f7..f9ead3a94 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -30,18 +30,18 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import com.beardedhen.androidbootstrap.BootstrapButton; import com.google.zxing.integration.android.IntentIntegratorSupportV4; import com.google.zxing.integration.android.IntentResult; public class ImportKeysQrCodeFragment extends Fragment { private ImportKeysActivity mImportActivity; - private Button mButton; + private BootstrapButton mButton; private TextView mText; private ProgressBar mProgress; @@ -66,7 +66,7 @@ public class ImportKeysQrCodeFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_qr_code_fragment, container, false); - mButton = (Button) view.findViewById(R.id.import_qrcode_button); + mButton = (BootstrapButton) view.findViewById(R.id.import_qrcode_button); mText = (TextView) view.findViewById(R.id.import_qrcode_text); mProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 106c8ebef..c985f1f60 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -24,12 +24,13 @@ import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnClickListener; -import android.widget.Button; +import android.view.ViewGroup; + +import com.beardedhen.androidbootstrap.BootstrapButton; public class ImportKeysServerFragment extends Fragment { - private Button mButton; + private BootstrapButton mButton; /** * Creates new instance of this fragment @@ -50,7 +51,7 @@ public class ImportKeysServerFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_keyserver_fragment, container, false); - mButton = (Button) view.findViewById(R.id.import_keyserver_button); + mButton = (BootstrapButton) view.findViewById(R.id.import_keyserver_button); mButton.setOnClickListener(new OnClickListener() { @Override diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListActivity.java deleted file mode 100644 index 7b844fe08..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListActivity.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.service.KeychainIntentService; -import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.R; - -import android.app.ProgressDialog; -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -public class KeyListActivity extends SherlockFragmentActivity { - - protected String mExportFilename = Constants.path.APP_DIR + "/"; - - protected String mImportData; - protected boolean mDeleteAfterImport = false; - - protected int mKeyType; - - FileDialogFragment mFileDialog; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); - - handleActions(getIntent()); - } - - // TODO: needed? - // @Override - // protected void onNewIntent(Intent intent) { - // super.onNewIntent(intent); - // handleActions(intent); - // } - - protected void handleActions(Intent intent) { - String action = intent.getAction(); - Bundle extras = intent.getExtras(); - - if (extras == null) { - extras = new Bundle(); - } - - /** - * Android Standard Actions - */ - String searchString = null; - if (Intent.ACTION_SEARCH.equals(action)) { - searchString = extras.getString(SearchManager.QUERY); - if (searchString != null && searchString.trim().length() == 0) { - searchString = null; - } - } - - // if (searchString == null) { - // mFilterLayout.setVisibility(View.GONE); - // } else { - // mFilterLayout.setVisibility(View.VISIBLE); - // mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - // } - // - // if (mListAdapter != null) { - // mListAdapter.cleanup(); - // } - // mListAdapter = new KeyListAdapter(this, searchString); - // mList.setAdapter(mListAdapter); - - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - try { - String path = data.getData().getPath(); - Log.d(Constants.TAG, "path=" + path); - - // set filename used in export/import dialogs - mFileDialog.setFilename(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); - } - } - return; - } - - default: { - break; - } - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - // TODO: reimplement! - // menu.add(3, Id.menu.option.search, 0, R.string.menu_search) - // .setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.add(0, Id.menu.option.import_from_file, 5, R.string.menu_import_from_file) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, Id.menu.option.export_keys, 6, R.string.menu_export_keys).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - - case Id.menu.option.import_from_file: { - Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); - startActivityForResult(intentImportFromFile, 0); - return true; - } - - case Id.menu.option.export_keys: { - showExportKeysDialog(-1); - return true; - } - - // case Id.menu.option.search: - // startSearch("", false, null, false); - // return true; - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - /** - * Show dialog where to export keys - * - * @param keyRingMasterKeyId - * if -1 export all keys - */ - public void showExportKeysDialog(final long keyRingMasterKeyId) { - // Message is received after file is selected - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == FileDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); - - exportKeys(keyRingMasterKeyId); - } - } - }; - - // Create a new Messenger for the communication back - final Messenger messenger = new Messenger(returnHandler); - - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - String title = null; - if (keyRingMasterKeyId != -1) { - // single key export - title = getString(R.string.title_export_key); - } else { - title = getString(R.string.title_export_keys); - } - - String message = null; - if (mKeyType == Id.type.public_key) { - message = getString(R.string.specify_file_to_export_to); - } else { - message = getString(R.string.specify_file_to_export_secret_keys_to); - } - - mFileDialog = FileDialogFragment.newInstance(messenger, title, message, - mExportFilename, null, Id.request.filename); - - mFileDialog.show(getSupportFragmentManager(), "fileDialog"); - } - }); - } - - /** - * Show dialog to delete key - * - * @param keyRingId - */ - public void showDeleteKeyDialog(long keyRingId) { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { - // no further actions needed - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - keyRingId, mKeyType); - - deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog"); - } - - /** - * Export keys - * - * @param keyRingMasterKeyId - * if -1 export all keys - */ - public void exportKeys(long keyRingMasterKeyId) { - Log.d(Constants.TAG, "exportKeys started"); - - // Send all information needed to service to export key in other thread - Intent intent = new Intent(this, KeychainIntentService.class); - - intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING); - - // fill values for this action - Bundle data = new Bundle(); - - data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename); - data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, mKeyType); - - if (keyRingMasterKeyId == -1) { - data.putBoolean(KeychainIntentService.EXPORT_ALL, true); - } else { - data.putLong(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, keyRingMasterKeyId); - } - - intent.putExtra(KeychainIntentService.EXTRA_DATA, data); - - // Message is received after exporting is done in ApgService - KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(this, - R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { - public void handleMessage(Message message) { - // handle messages by standard ApgHandler first - super.handleMessage(message); - - if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) { - // get returned data bundle - Bundle returnData = message.getData(); - - int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT); - String toastMessage; - if (exported == 1) { - toastMessage = getString(R.string.key_exported); - } else if (exported > 0) { - toastMessage = getString(R.string.keys_exported, exported); - } else { - toastMessage = getString(R.string.no_keys_exported); - } - Toast.makeText(KeyListActivity.this, toastMessage, Toast.LENGTH_SHORT).show(); - - } - }; - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(exportHandler); - intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger); - - // show progress dialog - exportHandler.showProgressDialog(this); - - // start service with intent - startService(intent); - } -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListFragment.java deleted file mode 100644 index 0d61b1108..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.widget.ExpandableListFragment; -import org.sufficientlysecure.keychain.R; - -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; - -public class KeyListFragment extends ExpandableListFragment { - protected KeyListActivity mKeyListActivity; - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - mKeyListActivity = (KeyListActivity) getActivity(); - - // register long press context menu - registerForContextMenu(getListView()); - - // Give some text to display if there is no data. In a real - // application this would come from a resource. - setEmptyText(getString(R.string.list_empty)); - } - - /** - * Context Menu on Long Click - */ - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - menu.add(0, Id.menu.export, 5, R.string.menu_export_key); - menu.add(0, Id.menu.delete, 111, R.string.menu_delete_key); - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo(); - - // expInfo.id would also return row id of childs, but we always want to get the row id of - // the group item, thus we are using the following way - int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition); - long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition); - - switch (item.getItemId()) { - case Id.menu.export: - long masterKeyId = ProviderHelper.getPublicMasterKeyId(mKeyListActivity, keyRingRowId); - if (masterKeyId == -1) { - masterKeyId = ProviderHelper.getSecretMasterKeyId(mKeyListActivity, keyRingRowId); - } - - mKeyListActivity.showExportKeysDialog(masterKeyId); - return true; - - case Id.menu.delete: - mKeyListActivity.showDeleteKeyDialog(keyRingRowId); - return true; - - default: - return super.onContextItemSelected(item); - - } - } - -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java index 95a3dd3b1..204939610 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicActivity.java @@ -20,102 +20,79 @@ package org.sufficientlysecure.keychain.ui; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; +import org.sufficientlysecure.keychain.helper.ExportHelper; import android.content.Intent; import android.os.Bundle; -public class KeyListPublicActivity extends KeyListActivity { +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +public class KeyListPublicActivity extends DrawerActivity { + + ExportHelper mExportHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mKeyType = Id.type.public_key; + mExportHelper = new ExportHelper(this); setContentView(R.layout.key_list_public_activity); - mExportFilename = Constants.path.APP_DIR + "/pubexport.asc"; + // now setup navigation drawer in DrawerActivity... + setupDrawerNavigation(savedInstanceState); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - menu.add(1, Id.menu.option.key_server, 1, R.string.menu_key_server) - .setIcon(R.drawable.ic_menu_search_list) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.add(1, Id.menu.option.import_from_qr_code, 2, R.string.menu_import_from_qr_code) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(1, Id.menu.option.import_from_nfc, 3, R.string.menu_import_from_nfc) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - + getSupportMenuInflater().inflate(R.menu.key_list_public, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case Id.menu.option.key_server: { - startActivityForResult(new Intent(this, KeyServerQueryActivity.class), 0); + case R.id.menu_key_list_public_import: + Intent intentImport = new Intent(this, ImportKeysActivity.class); + startActivityForResult(intentImport, Id.request.import_from_qr_code); return true; - } - case Id.menu.option.import_from_file: { - Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE); - startActivityForResult(intentImportFromFile, 0); + case R.id.menu_key_list_public_export: + mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.path.APP_DIR + + "/pubexport.asc"); return true; - } - - case Id.menu.option.import_from_qr_code: { - Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_QR_CODE); - startActivityForResult(intentImportFromFile, Id.request.import_from_qr_code); - - return true; - } - - case Id.menu.option.import_from_nfc: { - Intent intentImportFromFile = new Intent(this, ImportKeysActivity.class); - intentImportFromFile.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_NFC); - startActivityForResult(intentImportFromFile, 0); - - return true; - } - - default: { + default: return super.onOptionsItemSelected(item); } - } } - // @Override - // protected void onActivityResult(int requestCode, int resultCode, Intent data) { - // switch (requestCode) { - // case Id.request.look_up_key_id: { - // if (resultCode == RESULT_CANCELED || data == null - // || data.getStringExtra(KeyServerQueryActivity.RESULT_EXTRA_TEXT) == null) { - // return; - // } - // - // Intent intent = new Intent(this, KeyListPublicActivity.class); - // intent.setAction(KeyListPublicActivity.ACTION_IMPORT); - // intent.putExtra(KeyListPublicActivity.EXTRA_TEXT, - // data.getStringExtra(KeyListActivity.EXTRA_TEXT)); - // handleActions(intent); - // break; - // } - // - // default: { - // super.onActivityResult(requestCode, resultCode, data); - // break; - // } - // } - // } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!mExportHelper.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } + // switch (requestCode) { + // case Id.request.look_up_key_id: { + // if (resultCode == RESULT_CANCELED || data == null + // || data.getStringExtra(KeyServerQueryActivity.RESULT_EXTRA_TEXT) == null) { + // return; + // } + // + // Intent intent = new Intent(this, KeyListPublicActivity.class); + // intent.setAction(KeyListPublicActivity.ACTION_IMPORT); + // intent.putExtra(KeyListPublicActivity.EXTRA_TEXT, + // data.getStringExtra(KeyListActivity.EXTRA_TEXT)); + // handleActions(intent); + // break; + // } + // + // default: { + // super.onActivityResult(requestCode, resultCode, data); + // break; + // } + // } + } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java index 0fdcea809..ea088efca 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListPublicFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Dominik Schürmann + * Copyright (C) 2013 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,171 +17,205 @@ package org.sufficientlysecure.keychain.ui; -import org.spongycastle.openpgp.PGPPublicKeyRing; +import java.util.Set; + import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.ui.adapter.KeyListAdapter; -import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.KeyListPublicAdapter; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import se.emilsjolander.stickylistheaders.ApiLevelTooLowException; +import se.emilsjolander.stickylistheaders.StickyListHeadersListView; +import android.annotation.SuppressLint; import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.support.v4.app.LoaderManager; -import android.view.ContextMenu; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; -import android.view.ContextMenu.ContextMenuInfo; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.ListView; -public class KeyListPublicFragment extends KeyListFragment implements +import com.beardedhen.androidbootstrap.BootstrapButton; + +/** + * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses + * StickyListHeaders library which does not extend upon ListView. + */ +public class KeyListPublicFragment extends Fragment implements AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks { - private KeyListPublicActivity mKeyListPublicActivity; + private KeyListPublicAdapter mAdapter; + private StickyListHeadersListView mStickyList; - private KeyListAdapter mAdapter; + // empty layout + private BootstrapButton mButtonEmptyCreate; + private BootstrapButton mButtonEmptyImport; + + /** + * Load custom layout with StickyListView from library + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.key_list_public_fragment, container, false); + + mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create); + mButtonEmptyCreate.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intent = new Intent(getActivity(), EditKeyActivity.class); + intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); + intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); + intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, ""); // show user id view + startActivityForResult(intent, 0); + } + }); + + mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import); + mButtonEmptyImport.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent intentImportFromFile = new Intent(getActivity(), ImportKeysActivity.class); + startActivityForResult(intentImportFromFile, Id.request.import_from_qr_code); + } + }); + + return view; + } /** * Define Adapter and Loader on create of Activity */ + @SuppressLint("NewApi") @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mKeyListPublicActivity = (KeyListPublicActivity) getActivity(); + // mKeyListPublicActivity = (KeyListPublicActivity) getActivity(); + mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); - mAdapter = new KeyListAdapter(mKeyListPublicActivity, null, Id.type.public_key); - setListAdapter(mAdapter); + mStickyList.setOnItemClickListener(this); + mStickyList.setAreHeadersSticky(true); + mStickyList.setDrawingListUnderStickyHeader(false); + mStickyList.setFastScrollEnabled(true); + try { + mStickyList.setFastScrollAlwaysVisible(true); + } catch (ApiLevelTooLowException e) { + } + // this view is made visible if no data is available + mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); + + /* + * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only + * available for Android >= 3.0 + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { + + private int count = 0; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + android.view.MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.key_list_public_multi, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + Set positions = mAdapter.getCurrentCheckedPosition(); + + // get IDs for checked positions as long array + long[] ids = new long[positions.size()]; + int i = 0; + for (int pos : positions) { + ids[i] = mAdapter.getItemId(pos); + i++; + } + + switch (item.getItemId()) { + case R.id.menu_key_list_public_multi_encrypt: { + encrypt(ids); + + break; + } + case R.id.menu_key_list_public_multi_delete: { + showDeleteKeyDialog(ids); + + break; + } + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + count = 0; + mAdapter.clearSelection(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + if (checked) { + count++; + mAdapter.setNewSelection(position, checked); + } else { + count--; + mAdapter.removeSelection(position); + } + + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, count, count); + mode.setTitle(keysSelected); + } + + }); + } + + // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading // Start out with a progress indicator. - setListShown(false); + // setListShown(false); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new KeyListPublicAdapter(getActivity(), null, Id.type.public_key, USER_ID_INDEX); + mStickyList.setAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. - // id is -1 as the child cursors are numbered 0,...,n - getLoaderManager().initLoader(-1, null, this); - } - - /** - * Context Menu on Long Click - */ - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - menu.add(0, Id.menu.update, 1, R.string.menu_update_key); - menu.add(0, Id.menu.signKey, 2, R.string.menu_sign_key); - menu.add(0, Id.menu.exportToServer, 3, R.string.menu_export_key_to_server); - menu.add(0, Id.menu.share, 6, R.string.menu_share); - menu.add(0, Id.menu.share_qr_code, 7, R.string.menu_share_qr_code); - menu.add(0, Id.menu.share_nfc, 8, R.string.menu_share_nfc); - - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo(); - - // expInfo.id would also return row id of childs, but we always want to get the row id of - // the group item, thus we are using the following way - int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition); - long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition); - - switch (item.getItemId()) { - case Id.menu.update: - long updateKeyId = 0; - PGPPublicKeyRing updateKeyRing = ProviderHelper.getPGPPublicKeyRingByRowId( - mKeyListActivity, keyRingRowId); - if (updateKeyRing != null) { - updateKeyId = PgpKeyHelper.getMasterKey(updateKeyRing).getKeyID(); - } - if (updateKeyId == 0) { - // this shouldn't happen - return true; - } - - Intent queryIntent = new Intent(mKeyListActivity, KeyServerQueryActivity.class); - queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID_AND_RETURN); - queryIntent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, updateKeyId); - - // TODO: lookup?? - startActivityForResult(queryIntent, Id.request.look_up_key_id); - - return true; - - case Id.menu.exportToServer: - Intent uploadIntent = new Intent(mKeyListActivity, KeyServerUploadActivity.class); - uploadIntent.setAction(KeyServerUploadActivity.ACTION_EXPORT_KEY_TO_SERVER); - uploadIntent.putExtra(KeyServerUploadActivity.EXTRA_KEYRING_ROW_ID, (int)keyRingRowId); - startActivityForResult(uploadIntent, Id.request.export_to_server); - - return true; - - case Id.menu.signKey: - long keyId = 0; - PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByRowId( - mKeyListActivity, keyRingRowId); - if (signKeyRing != null) { - keyId = PgpKeyHelper.getMasterKey(signKeyRing).getKeyID(); - } - if (keyId == 0) { - // this shouldn't happen - return true; - } - - Intent signIntent = new Intent(mKeyListActivity, SignKeyActivity.class); - signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, keyId); - startActivity(signIntent); - - return true; - - case Id.menu.share_qr_code: - // get master key id using row id - long masterKeyId = ProviderHelper.getPublicMasterKeyId(mKeyListActivity, keyRingRowId); - - Intent qrCodeIntent = new Intent(mKeyListActivity, ShareActivity.class); - qrCodeIntent.setAction(ShareActivity.ACTION_SHARE_KEYRING_WITH_QR_CODE); - qrCodeIntent.putExtra(ShareActivity.EXTRA_MASTER_KEY_ID, masterKeyId); - startActivityForResult(qrCodeIntent, 0); - - return true; - - case Id.menu.share_nfc: - // get master key id using row id - long masterKeyId2 = ProviderHelper.getPublicMasterKeyId(mKeyListActivity, keyRingRowId); - - Intent nfcIntent = new Intent(mKeyListActivity, ShareNfcBeamActivity.class); - nfcIntent.setAction(ShareNfcBeamActivity.ACTION_SHARE_KEYRING_WITH_NFC); - nfcIntent.putExtra(ShareNfcBeamActivity.EXTRA_MASTER_KEY_ID, masterKeyId2); - startActivityForResult(nfcIntent, 0); - - return true; - - case Id.menu.share: - // get master key id using row id - long masterKeyId3 = ProviderHelper.getPublicMasterKeyId(mKeyListActivity, keyRingRowId); - - Intent shareIntent = new Intent(mKeyListActivity, ShareActivity.class); - shareIntent.setAction(ShareActivity.ACTION_SHARE_KEYRING); - shareIntent.putExtra(ShareActivity.EXTRA_MASTER_KEY_ID, masterKeyId3); - startActivityForResult(shareIntent, 0); - - return true; - - default: - return super.onContextItemSelected(item); - - } + getLoaderManager().initLoader(0, null, this); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, UserIds.USER_ID }; + static final int USER_ID_INDEX = 2; + static final String SORT_ORDER = UserIds.USER_ID + " ASC"; @Override @@ -199,14 +233,17 @@ public class KeyListPublicFragment extends KeyListFragment implements public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.setGroupCursor(data); + mAdapter.swapCursor(data); + mStickyList.setAdapter(mAdapter); + + // NOTE: Not supported by StickyListHeader, thus no indicator is shown while loading // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } + // if (isResumed()) { + // setListShown(true); + // } else { + // setListShownNoAnimation(true); + // } } @Override @@ -214,7 +251,43 @@ public class KeyListPublicFragment extends KeyListFragment implements // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. - mAdapter.setGroupCursor(null); + mAdapter.swapCursor(null); } -} + /** + * On click on item, start key view activity + */ + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + Intent detailsIntent = new Intent(getActivity(), ViewKeyActivity.class); + detailsIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(id))); + startActivity(detailsIntent); + } + + public void encrypt(long[] keyRingRowIds) { + // get master key ids from row ids + long[] keyRingIds = new long[keyRingRowIds.length]; + for (int i = 0; i < keyRingRowIds.length; i++) { + keyRingIds[i] = ProviderHelper.getPublicMasterKeyId(getActivity(), keyRingRowIds[i]); + } + + Intent intent = new Intent(getActivity(), EncryptActivity.class); + intent.setAction(EncryptActivity.ACTION_ENCRYPT); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingIds); + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, 0); + } + + /** + * Show dialog to delete key + * + * @param keyRingRowIds + */ + public void showDeleteKeyDialog(long[] keyRingRowIds) { + DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, + keyRingRowIds, Id.type.public_key); + + deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); + } + +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java index 822c73872..34a053d25 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretActivity.java @@ -1,18 +1,18 @@ /* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (C) 2010 Thialfihar + * Copyright (C) 2013 Dominik Schürmann * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ExportHelper; import android.content.Intent; import android.os.Bundle; @@ -27,47 +28,53 @@ import android.os.Bundle; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; -public class KeyListSecretActivity extends KeyListActivity { +public class KeyListSecretActivity extends DrawerActivity { + + ExportHelper mExportHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mKeyType = Id.type.secret_key; + mExportHelper = new ExportHelper(this); setContentView(R.layout.key_list_secret_activity); - mExportFilename = Constants.path.APP_DIR + "/secexport.asc"; + // now setup navigation drawer in DrawerActivity... + setupDrawerNavigation(savedInstanceState); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - menu.add(1, Id.menu.option.create, 1, R.string.menu_create_key).setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(1, Id.menu.option.createExpert, 2, R.string.menu_create_key_expert).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER); - + getSupportMenuInflater().inflate(R.menu.key_list_secret, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case Id.menu.option.create: { + case R.id.menu_key_list_secret_create: createKey(); - return true; - } - case Id.menu.option.createExpert: { + return true; + case R.id.menu_key_list_secret_create_expert: createKeyExpert(); - return true; - } - default: { + return true; + case R.id.menu_key_list_secret_export: + mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.path.APP_DIR + + "/secexport.asc"); + + return true; + case R.id.menu_key_list_secret_import: + Intent intentImport = new Intent(this, ImportKeysActivity.class); + startActivityForResult(intentImport, Id.request.import_from_qr_code); + + return true; + default: return super.onOptionsItemSelected(item); } - } } private void createKey() { @@ -84,12 +91,11 @@ public class KeyListSecretActivity extends KeyListActivity { startActivityForResult(intent, 0); } - void editKey(long masterKeyId, boolean masterCanSign) { - Intent intent = new Intent(this, EditKeyActivity.class); - intent.setAction(EditKeyActivity.ACTION_EDIT_KEY); - intent.putExtra(EditKeyActivity.EXTRA_MASTER_KEY_ID, masterKeyId); - intent.putExtra(EditKeyActivity.EXTRA_MASTER_CAN_SIGN, masterCanSign); - startActivityForResult(intent, 0); + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!mExportHelper.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java index 4bbf3d6ef..0e2d22e1e 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyListSecretFragment.java @@ -1,116 +1,168 @@ /* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (C) 2010 Thialfihar + * Copyright (C) 2013 Dominik Schürmann * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; +import java.util.Set; + import org.sufficientlysecure.keychain.Id; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.adapter.KeyListAdapter; +import org.sufficientlysecure.keychain.ui.adapter.KeyListSecretAdapter; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import android.annotation.SuppressLint; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView.OnItemClickListener; -public class KeyListSecretFragment extends KeyListFragment implements - LoaderManager.LoaderCallbacks { +import com.actionbarsherlock.app.SherlockListFragment; + +public class KeyListSecretFragment extends SherlockListFragment implements + LoaderManager.LoaderCallbacks, OnItemClickListener { private KeyListSecretActivity mKeyListSecretActivity; - - private KeyListAdapter mAdapter; + private KeyListSecretAdapter mAdapter; /** * Define Adapter and Loader on create of Activity */ + @SuppressLint("NewApi") @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mKeyListSecretActivity = (KeyListSecretActivity) getActivity(); - mAdapter = new KeyListAdapter(mKeyListSecretActivity, null, Id.type.secret_key); - setListAdapter(mAdapter); + getListView().setOnItemClickListener(this); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText(getString(R.string.list_empty)); + + /* + * ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only + * available for Android >= 3.0 + */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() { + + private int count = 0; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + android.view.MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.key_list_secret_multi, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + Set positions = mAdapter.getCurrentCheckedPosition(); + + // get IDs for checked positions as long array + long[] ids = new long[positions.size()]; + int i = 0; + for (int pos : positions) { + ids[i] = mAdapter.getItemId(pos); + i++; + } + + switch (item.getItemId()) { + case R.id.menu_key_list_public_multi_delete: { + showDeleteKeyDialog(ids); + + break; + } + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + count = 0; + mAdapter.clearSelection(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + if (checked) { + count++; + mAdapter.setNewSelection(position, checked); + } else { + count--; + mAdapter.removeSelection(position); + } + + String keysSelected = getResources().getQuantityString( + R.plurals.key_list_selected_keys, count, count); + mode.setTitle(keysSelected); + } + + }); + } + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); // Start out with a progress indicator. setListShown(false); + // Create an empty adapter we will use to display the loaded data. + mAdapter = new KeyListSecretAdapter(mKeyListSecretActivity, null, 0); + setListAdapter(mAdapter); + // Prepare the loader. Either re-connect with an existing one, // or start a new one. - // id is -1 as the child cursors are numbered 0,...,n - getLoaderManager().initLoader(-1, null, this); - } - - /** - * Context Menu on Long Click - */ - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - menu.add(0, Id.menu.edit, 0, R.string.menu_edit_key); - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem item) { - ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) item.getMenuInfo(); - - // expInfo.id would also return row id of childs, but we always want to get the row id of - // the group item, thus we are using the following way - int groupPosition = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition); - long keyRingRowId = getExpandableListAdapter().getGroupId(groupPosition); - - // get master key id using row id - long masterKeyId = ProviderHelper - .getSecretMasterKeyId(mKeyListSecretActivity, keyRingRowId); - - boolean masterCanSign = ProviderHelper.getSecretMasterKeyCanSign(mKeyListSecretActivity, - keyRingRowId); - - switch (item.getItemId()) { - case Id.menu.edit: - mKeyListSecretActivity.editKey(masterKeyId, masterCanSign); - - return true; - - default: - return super.onContextItemSelected(item); - - } + getLoaderManager().initLoader(0, null, this); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, UserIds.USER_ID }; + static final String SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; - static final String SORT_ORDER = UserIds.USER_ID + " ASC"; - - @Override public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. Uri baseUri = KeyRings.buildSecretKeyRingsUri(); // Now create and return a CursorLoader that will take care of @@ -118,11 +170,10 @@ public class KeyListSecretFragment extends KeyListFragment implements return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, SORT_ORDER); } - @Override public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.setGroupCursor(data); + mAdapter.swapCursor(data); // The list should now be shown. if (isResumed()) { @@ -132,12 +183,33 @@ public class KeyListSecretFragment extends KeyListFragment implements } } - @Override public void onLoaderReset(Loader loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. - mAdapter.setGroupCursor(null); + mAdapter.swapCursor(null); } + /** + * On click on item, start key view activity + */ + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + Intent editIntent = new Intent(mKeyListSecretActivity, EditKeyActivity.class); + editIntent.setData(KeychainContract.KeyRings.buildSecretKeyRingsUri(Long.toString(id))); + editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY); + startActivityForResult(editIntent, 0); + } + + /** + * Show dialog to delete key + * + * @param keyRingRowIds + */ + public void showDeleteKeyDialog(long[] keyRingRowIds) { + DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(null, + keyRingRowIds, Id.type.secret_key); + + deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); + } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java index b4679f9d5..6073e6b80 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerQueryActivity.java @@ -111,7 +111,7 @@ public class KeyServerQueryActivity extends SherlockFragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.key_server_query_layout); + setContentView(R.layout.key_server_query); mQuery = (EditText)findViewById(R.id.query); mSearch = (Button)findViewById(R.id.btn_search); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java index 996637c7a..8a32ea513 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/KeyServerUploadActivity.java @@ -76,7 +76,7 @@ public class KeyServerUploadActivity extends SherlockFragmentActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.key_server_export_layout); + setContentView(R.layout.key_server_export); export = (Button) findViewById(R.id.btn_export_to_server); keyServer = (Spinner) findViewById(R.id.keyServer); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java deleted file mode 100644 index 9a270e60b..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; - -public class MainActivity extends SherlockActivity { - - public void manageKeysOnClick(View view) { - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(new Intent(this, KeyListPublicActivity.class), 0); - } - - public void myKeysOnClick(View view) { - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(new Intent(this, KeyListSecretActivity.class), 0); - } - - public void encryptOnClick(View view) { - Intent intent = new Intent(MainActivity.this, EncryptActivity.class); - intent.setAction(EncryptActivity.ACTION_ENCRYPT); - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, 0); - } - - public void decryptOnClick(View view) { - Intent intent = new Intent(MainActivity.this, DecryptActivity.class); - intent.setAction(DecryptActivity.ACTION_DECRYPT); - // used instead of startActivity set actionbar based on callingPackage - startActivityForResult(intent, 0); - } - - public void scanQrcodeOnClick(View view) { - Intent intent = new Intent(this, ImportKeysActivity.class); - startActivityForResult(intent, 0); - } - - public void helpOnClick(View view) { - startActivity(new Intent(this, HelpActivity.class)); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) - .setIcon(R.drawable.ic_menu_settings) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(0, Id.menu.option.crypto_consumers, 0, R.string.menu_api_app_settings) - .setIcon(R.drawable.ic_menu_settings) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case Id.menu.option.preferences: - startActivity(new Intent(this, PreferencesActivity.class)); - return true; - - case Id.menu.option.crypto_consumers: - startActivity(new Intent(this, RegisteredAppsListActivity.class)); - return true; - - default: - break; - - } - return false; - } - -} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index 6607ab4d5..46bbd05c9 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -20,13 +20,9 @@ import org.spongycastle.bcpg.HashAlgorithmTags; import org.spongycastle.openpgp.PGPEncryptedData; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; -import org.sufficientlysecure.keychain.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockPreferenceActivity; -import com.actionbarsherlock.view.MenuItem; import android.content.Intent; import android.os.Bundle; @@ -34,6 +30,9 @@ import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockPreferenceActivity; + public class PreferencesActivity extends SherlockPreferenceActivity { private IntegerListPreference mPassPhraseCacheTtl = null; private IntegerListPreference mEncryptionAlgorithm = null; @@ -52,8 +51,8 @@ public class PreferencesActivity extends SherlockPreferenceActivity { final ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); addPreferencesFromResource(R.xml.preferences); @@ -219,22 +218,4 @@ public class PreferencesActivity extends SherlockPreferenceActivity { } } } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - - default: - break; - - } - return false; - } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java index b0711ed31..83669a523 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyActivity.java @@ -137,21 +137,4 @@ public class SelectSecretKeyActivity extends SherlockFragmentActivity { return true; } - /** - * Menu Options - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - // app icon in Action Bar clicked; go home - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareActivity.java deleted file mode 100644 index 159b2b63a..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareActivity.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import java.util.ArrayList; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; - -import android.content.Intent; -import android.os.Bundle; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -public class ShareActivity extends SherlockFragmentActivity { - // Actions for internal use only: - public static final String ACTION_SHARE_KEYRING = Constants.INTENT_PREFIX + "SHARE_KEYRING"; - public static final String ACTION_SHARE_KEYRING_WITH_QR_CODE = Constants.INTENT_PREFIX - + "SHARE_KEYRING_WITH_QR_CODE"; - - public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - handleActions(getIntent()); - } - - protected void handleActions(Intent intent) { - String action = intent.getAction(); - Bundle extras = intent.getExtras(); - - if (extras == null) { - extras = new Bundle(); - } - - long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID); - - // get public keyring as ascii armored string - ArrayList keyringArmored = ProviderHelper.getPublicKeyRingsAsArmoredString(this, - new long[] { masterKeyId }); - - if (ACTION_SHARE_KEYRING.equals(action)) { - // let user choose application - Intent sendIntent = new Intent(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_TEXT, keyringArmored.get(0)); - sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent, - getResources().getText(R.string.action_share_key_with))); - } else if (ACTION_SHARE_KEYRING_WITH_QR_CODE.equals(action)) { - ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(keyringArmored - .get(0)); - dialog.show(getSupportFragmentManager(), "qrCodeShareDialog"); - } - - // close this activity - // TODO: finish() would also close dialog... - // integrate this into new KeyViewActivity when ready - // finish(); - } -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareNfcBeamActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareNfcBeamActivity.java deleted file mode 100644 index db6a156c7..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ShareNfcBeamActivity.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2013 Dominik Schürmann - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui; - -import org.sufficientlysecure.htmltextview.HtmlTextView; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.helper.ActionBarHelper; -import org.sufficientlysecure.keychain.provider.ProviderHelper; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.nfc.NfcAdapter; -import android.nfc.NfcAdapter.CreateNdefMessageCallback; -import android.nfc.NfcAdapter.OnNdefPushCompleteCallback; -import android.nfc.NfcEvent; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Parcelable; -import android.provider.Settings; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -@TargetApi(Build.VERSION_CODES.JELLY_BEAN) -public class ShareNfcBeamActivity extends SherlockFragmentActivity implements - CreateNdefMessageCallback, OnNdefPushCompleteCallback { - public static final String ACTION_SHARE_KEYRING_WITH_NFC = Constants.INTENT_PREFIX - + "SHARE_KEYRING_WITH_NFC"; - - public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; - - NfcAdapter mNfcAdapter; - - byte[] mSharedKeyringBytes; - - private static final int MESSAGE_SENT = 1; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - Toast.makeText(this, - getString(R.string.error) + ": " + getString(R.string.error_jelly_bean_needed), - Toast.LENGTH_LONG).show(); - finish(); - } else { - // Check for available NFC Adapter - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (mNfcAdapter == null) { - Toast.makeText(this, - getString(R.string.error) + ": " + getString(R.string.error_nfc_needed), - Toast.LENGTH_LONG).show(); - finish(); - } else { - // handle actions after verifying that nfc works... - handleActions(getIntent()); - } - } - } - - protected void handleActions(Intent intent) { - String action = intent.getAction(); - Bundle extras = intent.getExtras(); - - if (extras == null) { - extras = new Bundle(); - } - - if (ACTION_SHARE_KEYRING_WITH_NFC.equals(action)) { - long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID); - - // get public keyring as byte array - mSharedKeyringBytes = ProviderHelper.getPublicKeyRingsAsByteArray(this, - new long[] { masterKeyId }); - - // Register callback to set NDEF message - mNfcAdapter.setNdefPushMessageCallback(this, this); - // Register callback to listen for message-sent success - mNfcAdapter.setOnNdefPushCompleteCallback(this, this); - } - } - - /** - * Parses the NDEF Message from the intent and prints to the TextView - */ - void handleActionNdefDiscovered(Intent intent) { - Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); - // only one message sent during the beam - NdefMessage msg = (NdefMessage) rawMsgs[0]; - // record 0 contains the MIME type, record 1 is the AAR, if present - byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); - - Intent importIntent = new Intent(this, ImportKeysActivity.class); - importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY); - importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes); - - finish(); - - startActivity(importIntent); - } - - private void buildView() { - setContentView(R.layout.share_nfc_beam); - - HtmlTextView aboutTextView = (HtmlTextView) findViewById(R.id.nfc_beam_text); - - // load html from raw resource (Parsing handled by HtmlTextView library) - aboutTextView.setHtmlFromRawResource(this, R.raw.nfc_beam_share); - - // no flickering when clicking textview for Android < 4 - aboutTextView.setTextColor(getResources().getColor(android.R.color.black)); - - // set actionbar without home button if called from another app - ActionBarHelper.setBackButton(this); - } - - /** - * Implementation for the CreateNdefMessageCallback interface - */ - @Override - public NdefMessage createNdefMessage(NfcEvent event) { - /** - * When a device receives a push with an AAR in it, the application specified in the AAR is - * guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to - * guarantee that this activity starts when receiving a beamed message. For now, this code - * uses the tag dispatch system. - */ - NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, - mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); - return msg; - } - - /** - * Implementation for the OnNdefPushCompleteCallback interface - */ - @Override - public void onNdefPushComplete(NfcEvent arg0) { - // A handler is needed to send messages to the activity when this - // callback occurs, because it happens from a binder thread - mHandler.obtainMessage(MESSAGE_SENT).sendToTarget(); - } - - /** This handler receives a message from onNdefPushComplete */ - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_SENT: - Toast.makeText(getApplicationContext(), R.string.nfc_successfull, Toast.LENGTH_LONG) - .show(); - break; - } - } - }; - - @Override - public void onResume() { - super.onResume(); - // Check to see that the Activity started due to an Android Beam - if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - handleActionNdefDiscovered(getIntent()); - } else { - // build view only when sending nfc, not when receiving, as it gets directly into Import - // activity on receiving - buildView(); - } - } - - @Override - public void onNewIntent(Intent intent) { - // onResume gets called after this to handle the intent - setIntent(intent); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.nfc_beam, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - // app icon in Action Bar clicked; go to KeyListPublicActivity - Intent intent = new Intent(this, KeyListPublicActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - - case R.id.menu_settings: - Intent intentSettings = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); - startActivity(intentSettings); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java index c2fe1315b..6abdddee6 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java @@ -68,7 +68,7 @@ public class SignKeyActivity extends SherlockFragmentActivity { super.onCreate(savedInstanceState); // check we havent already signed it - setContentView(R.layout.sign_key_layout); + setContentView(R.layout.sign_key_activity); final ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayShowTitleEnabled(true); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java new file mode 100644 index 000000000..e2f90e87c --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * Copyright (C) 2013 Bahtiar 'kalkin' Gadimov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui; + +import java.util.ArrayList; +import java.util.Date; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; +import org.sufficientlysecure.keychain.helper.ExportHelper; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter; +import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter; +import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.NfcAdapter.CreateNdefMessageCallback; +import android.nfc.NfcAdapter.OnNdefPushCompleteCallback; +import android.nfc.NfcEvent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.text.format.DateFormat; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; +import com.beardedhen.androidbootstrap.BootstrapButton; + +@SuppressLint("NewApi") +public class ViewKeyActivity extends SherlockFragmentActivity implements CreateNdefMessageCallback, + OnNdefPushCompleteCallback, LoaderManager.LoaderCallbacks { + + ExportHelper mExportHelper; + + private Uri mDataUri; + + private PGPPublicKey mPublicKey; + + private TextView mName; + private TextView mEmail; + private TextView mComment; + private TextView mAlgorithm; + private TextView mKeyId; + private TextView mExpiry; + private TextView mCreation; + private TextView mFingerprint; + private BootstrapButton mActionEncrypt; + + private ListView mUserIds; + private ListView mKeys; + + // NFC + private NfcAdapter mNfcAdapter; + private byte[] mSharedKeyringBytes; + private static final int NFC_SENT = 1; + + private static final int LOADER_ID_KEYRING = 0; + private static final int LOADER_ID_USER_IDS = 1; + private static final int LOADER_ID_KEYS = 2; + private ViewKeyUserIdsAdapter mUserIdsAdapter; + private ViewKeyKeysAdapter mKeysAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mExportHelper = new ExportHelper(this); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setIcon(android.R.color.transparent); + getSupportActionBar().setHomeButtonEnabled(true); + + setContentView(R.layout.view_key_activity); + + mName = (TextView) findViewById(R.id.name); + mEmail = (TextView) findViewById(R.id.email); + mComment = (TextView) findViewById(R.id.comment); + mKeyId = (TextView) findViewById(R.id.key_id); + mAlgorithm = (TextView) findViewById(R.id.algorithm); + mCreation = (TextView) findViewById(R.id.creation); + mExpiry = (TextView) findViewById(R.id.expiry); + mFingerprint = (TextView) findViewById(R.id.fingerprint); + mActionEncrypt = (BootstrapButton) findViewById(R.id.action_encrypt); + mUserIds = (ListView) findViewById(R.id.user_ids); + mKeys = (ListView) findViewById(R.id.keys); + + Intent intent = getIntent(); + mDataUri = intent.getData(); + if (mDataUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mDataUri); + loadData(mDataUri); + initNfc(mDataUri); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getSupportMenuInflater().inflate(R.menu.key_view, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + Intent homeIntent = new Intent(this, KeyListPublicActivity.class); + homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(homeIntent); + return true; + case R.id.menu_key_view_update: + updateFromKeyserver(mDataUri); + return true; + case R.id.menu_key_view_sign: + signKey(mDataUri); + return true; + case R.id.menu_key_view_export_keyserver: + uploadToKeyserver(mDataUri); + return true; + case R.id.menu_key_view_export_file: + mExportHelper.showExportKeysDialog(mDataUri, Id.type.public_key, Constants.path.APP_DIR + + "/pubexport.asc"); + return true; + case R.id.menu_key_view_share_default: + shareKey(mDataUri); + return true; + case R.id.menu_key_view_share_qr_code: + shareKeyQrCode(mDataUri); + return true; + case R.id.menu_key_view_share_nfc: + shareNfc(); + return true; + case R.id.menu_key_view_share_clipboard: + copyToClipboard(mDataUri); + return true; + case R.id.menu_key_view_delete: { + // Message is received after key is deleted + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) { + setResult(RESULT_CANCELED); + finish(); + } + } + }; + + mExportHelper.deleteKey(mDataUri, Id.type.public_key, returnHandler); + return true; + } + } + return super.onOptionsItemSelected(item); + } + + private void loadData(Uri dataUri) { + // TODO: don't use pubkey object, use database!!! + + PGPPublicKeyRing ring = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, dataUri); + mPublicKey = ring.getPublicKey(); + + mKeyId.setText(PgpKeyHelper.shortifyFingerprint(PgpKeyHelper + .convertFingerprintToHex(mPublicKey.getFingerprint()))); + + String fingerprint = PgpKeyHelper.convertFingerprintToHex(mPublicKey.getFingerprint()); + fingerprint = fingerprint.replace(" ", "\n"); + mFingerprint.setText(fingerprint); + + // TODO: get image with getUserAttributes() on key and then PGPUserAttributeSubpacketVector + + Date expiryDate = PgpKeyHelper.getExpiryDate(mPublicKey); + if (expiryDate == null) { + mExpiry.setText(R.string.none); + } else { + mExpiry.setText(DateFormat.getDateFormat(getApplicationContext()).format(expiryDate)); + } + + mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format( + PgpKeyHelper.getCreationDate(mPublicKey))); + mAlgorithm.setText(PgpKeyHelper.getAlgorithmInfo(mPublicKey)); + + mActionEncrypt.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + long[] encryptionKeyIds = new long[] { mPublicKey.getKeyID() }; + Intent intent = new Intent(ViewKeyActivity.this, EncryptActivity.class); + intent.setAction(EncryptActivity.ACTION_ENCRYPT); + intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); + // used instead of startActivity set actionbar based on callingPackage + startActivityForResult(intent, 0); + } + }); + + mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0); + + mUserIds.setAdapter(mUserIdsAdapter); + // mUserIds.setEmptyView(findViewById(android.R.id.empty)); + // mUserIds.setClickable(true); + // mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { + // @Override + // public void onItemClick(AdapterView arg0, View arg1, int position, long id) { + // } + // }); + + mKeysAdapter = new ViewKeyKeysAdapter(this, null, 0); + + mKeys.setAdapter(mKeysAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this); + getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this); + } + + static final String[] KEYRING_PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, + UserIds.USER_ID }; + + static final String[] USER_IDS_PROJECTION = new String[] { UserIds._ID, UserIds.USER_ID, + UserIds.RANK, }; + // not the main user id + static final String USER_IDS_SELECTION = UserIds.RANK + " > 0 "; + static final String USER_IDS_SORT_ORDER = UserIds.USER_ID + " COLLATE LOCALIZED ASC"; + + static final String[] KEYS_PROJECTION = new String[] { Keys._ID, Keys.KEY_ID, + Keys.IS_MASTER_KEY, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN, + Keys.CAN_ENCRYPT, }; + static final String KEYS_SORT_ORDER = Keys.RANK + " ASC"; + + public Loader onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_KEYRING: { + Uri baseUri = mDataUri; + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(this, baseUri, KEYRING_PROJECTION, null, null, null); + } + case LOADER_ID_USER_IDS: { + Uri baseUri = UserIds.buildUserIdsUri(mDataUri); + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, USER_IDS_SELECTION, null, + USER_IDS_SORT_ORDER); + } + case LOADER_ID_KEYS: { + Uri baseUri = Keys.buildKeysUri(mDataUri); + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(this, baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_KEYRING: + if (data.moveToFirst()) { + String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(2)); + setTitle(mainUserId[0]); + mName.setText(mainUserId[0]); + mEmail.setText(mainUserId[1]); + mComment.setText(mainUserId[2]); + } + + break; + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(data); + break; + case LOADER_ID_KEYS: + mKeysAdapter.swapCursor(data); + break; + + default: + break; + } + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader loader) { + switch (loader.getId()) { + case LOADER_ID_KEYRING: + // TODO? + break; + case LOADER_ID_USER_IDS: + mUserIdsAdapter.swapCursor(null); + break; + case LOADER_ID_KEYS: + mKeysAdapter.swapCursor(null); + break; + default: + break; + } + } + + private void uploadToKeyserver(Uri dataUri) { + long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment()); + + Intent uploadIntent = new Intent(this, KeyServerUploadActivity.class); + uploadIntent.setAction(KeyServerUploadActivity.ACTION_EXPORT_KEY_TO_SERVER); + uploadIntent.putExtra(KeyServerUploadActivity.EXTRA_KEYRING_ROW_ID, (int) keyRingRowId); + startActivityForResult(uploadIntent, Id.request.export_to_server); + } + + private void updateFromKeyserver(Uri dataUri) { + long updateKeyId = 0; + PGPPublicKeyRing updateRing = (PGPPublicKeyRing) ProviderHelper + .getPGPKeyRing(this, dataUri); + + if (updateRing != null) { + updateKeyId = PgpKeyHelper.getMasterKey(updateRing).getKeyID(); + } + if (updateKeyId == 0) { + Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); + return; + } + + Intent signIntent = new Intent(this, SignKeyActivity.class); + signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, updateKeyId); + startActivity(signIntent); + + Intent queryIntent = new Intent(this, KeyServerQueryActivity.class); + queryIntent.setAction(KeyServerQueryActivity.ACTION_LOOK_UP_KEY_ID_AND_RETURN); + queryIntent.putExtra(KeyServerQueryActivity.EXTRA_KEY_ID, updateKeyId); + + // TODO: lookup?? + startActivityForResult(queryIntent, Id.request.look_up_key_id); + } + + private void signKey(Uri dataUri) { + long keyId = 0; + PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, dataUri); + + if (signKey != null) { + keyId = PgpKeyHelper.getMasterKey(signKey).getKeyID(); + } + if (keyId == 0) { + Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!"); + return; + } + + Intent signIntent = new Intent(this, SignKeyActivity.class); + signIntent.putExtra(SignKeyActivity.EXTRA_KEY_ID, keyId); + startActivity(signIntent); + } + + private void shareKey(Uri dataUri) { + // get public keyring as ascii armored string + long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); + ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri, + new long[] { masterKeyId }); + + // let user choose application + Intent sendIntent = new Intent(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, keyringArmored.get(0)); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, + getResources().getText(R.string.action_share_key_with))); + } + + private void shareKeyQrCode(Uri dataUri) { + // get public keyring as ascii armored string + long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); + ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri, + new long[] { masterKeyId }); + + ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(keyringArmored + .get(0)); + dialog.show(getSupportFragmentManager(), "shareQrCodeDialog"); + } + + private void copyToClipboard(Uri dataUri) { + // get public keyring as ascii armored string + long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); + ArrayList keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri, + new long[] { masterKeyId }); + + ClipboardReflection.copyToClipboard(this, keyringArmored.get(0)); + } + + private void shareNfc() { + ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance(); + dialog.show(getSupportFragmentManager(), "shareNfcDialog"); + } + + /** + * NFC: Initialize NFC sharing if OS and device supports it + */ + private void initNfc(Uri dataUri) { + // check if NFC Beam is supported (>= Android 4.1) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // Check for available NFC Adapter + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + if (mNfcAdapter != null) { + // init nfc + + // get public keyring as byte array + long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri); + mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri, + new long[] { masterKeyId }); + + // Register callback to set NDEF message + mNfcAdapter.setNdefPushMessageCallback(this, this); + // Register callback to listen for message-sent success + mNfcAdapter.setOnNdefPushCompleteCallback(this, this); + } + } + } + + /** + * NFC: Implementation for the CreateNdefMessageCallback interface + */ + @Override + public NdefMessage createNdefMessage(NfcEvent event) { + /** + * When a device receives a push with an AAR in it, the application specified in the AAR is + * guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to + * guarantee that this activity starts when receiving a beamed message. For now, this code + * uses the tag dispatch system. + */ + NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, + mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); + return msg; + } + + /** + * NFC: Implementation for the OnNdefPushCompleteCallback interface + */ + @Override + public void onNdefPushComplete(NfcEvent arg0) { + // A handler is needed to send messages to the activity when this + // callback occurs, because it happens from a binder thread + mNfcHandler.obtainMessage(NFC_SENT).sendToTarget(); + } + + /** + * NFC: This handler receives a message from onNdefPushComplete + */ + private final Handler mNfcHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case NFC_SENT: + Toast.makeText(getApplicationContext(), R.string.nfc_successfull, Toast.LENGTH_LONG) + .show(); + break; + } + } + }; + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!mExportHelper.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.java deleted file mode 100644 index e94934008..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListAdapter.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2012-2013 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui.adapter; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.helper.OtherHelper; -import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; -import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.R; - -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.MergeCursor; -import android.net.Uri; -import android.provider.BaseColumns; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorTreeAdapter; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class KeyListAdapter extends CursorTreeAdapter { - private Context mContext; - private LayoutInflater mInflater; - - protected int mKeyType; - - private static final int CHILD_KEY = 0; - private static final int CHILD_USER_ID = 1; - private static final int CHILD_FINGERPRINT = 2; - - public KeyListAdapter(Context context, Cursor groupCursor, int keyType) { - super(groupCursor, context); - mContext = context; - mInflater = LayoutInflater.from(context); - mKeyType = keyType; - } - - /** - * Inflate new view for group items - */ - @Override - public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_group_item, null); - } - - /** - * Binds TextViews from group view to results from database group cursor. - */ - @Override - protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { - int userIdIndex = cursor.getColumnIndex(UserIds.USER_ID); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknown_user_id); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - - String userId = cursor.getString(userIdIndex); - if (userId != null) { - String[] userIdSplit = OtherHelper.splitUserId(userId); - - if (userIdSplit[1] != null) { - mainUserIdRest.setText(userIdSplit[1]); - } - mainUserId.setText(userIdSplit[0]); - } - - if (mainUserId.getText().length() == 0) { - mainUserId.setText(R.string.unknown_user_id); - } - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } else { - mainUserIdRest.setVisibility(View.VISIBLE); - } - } - - /** - * Inflate new view for child items - */ - @Override - public View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) { - return mInflater.inflate(R.layout.key_list_child_item, null); - } - - /** - * Bind TextViews from view of childs based on query results - */ - @Override - protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { - LinearLayout keyLayout = (LinearLayout) view.findViewById(R.id.keyLayout); - LinearLayout userIdLayout = (LinearLayout) view.findViewById(R.id.userIdLayout); - - // first entry is fingerprint - if (cursor.getPosition() == 0) { - // show only userId layout - keyLayout.setVisibility(View.GONE); - userIdLayout.setVisibility(View.VISIBLE); - - String fingerprint = PgpKeyHelper.getFingerPrint(context, - cursor.getLong(cursor.getColumnIndex(Keys.KEY_ID))); - fingerprint = fingerprint.replace(" ", "\n"); - - TextView userId = (TextView) view.findViewById(R.id.userId); - if (userId == null) { - Log.d(Constants.TAG, "userId is null!"); - } - userId.setText(context.getString(R.string.fingerprint) + "\n" + fingerprint); - } else { - // differentiate between keys and userIds in MergeCursor - if (cursor.getColumnIndex(Keys.KEY_ID) != -1) { - keyLayout.setVisibility(View.VISIBLE); - userIdLayout.setVisibility(View.GONE); - - String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(cursor - .getColumnIndex(Keys.KEY_ID))); - String algorithmStr = PgpKeyHelper.getAlgorithmInfo( - cursor.getInt(cursor.getColumnIndex(Keys.ALGORITHM)), - cursor.getInt(cursor.getColumnIndex(Keys.KEY_SIZE))); - - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(keyIdStr); - - TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); - keyDetails.setText("(" + algorithmStr + ")"); - - ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); - if (cursor.getInt(cursor.getColumnIndex(Keys.IS_MASTER_KEY)) != 1) { - masterKeyIcon.setVisibility(View.INVISIBLE); - } else { - masterKeyIcon.setVisibility(View.VISIBLE); - } - - ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); - if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_CERTIFY)) != 1) { - certifyIcon.setVisibility(View.GONE); - } else { - certifyIcon.setVisibility(View.VISIBLE); - } - - ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); - if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_ENCRYPT)) != 1) { - encryptIcon.setVisibility(View.GONE); - } else { - encryptIcon.setVisibility(View.VISIBLE); - } - - ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); - if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_SIGN)) != 1) { - signIcon.setVisibility(View.GONE); - } else { - signIcon.setVisibility(View.VISIBLE); - } - } else { - keyLayout.setVisibility(View.GONE); - userIdLayout.setVisibility(View.VISIBLE); - - String userIdStr = cursor.getString(cursor.getColumnIndex(UserIds.USER_ID)); - - TextView userId = (TextView) view.findViewById(R.id.userId); - userId.setText(userIdStr); - } - } - } - - /** - * Given the group cursor, we start cursors for a fingerprint, keys, and userIds, which are - * merged together and build the child cursor - */ - @Override - protected Cursor getChildrenCursor(Cursor groupCursor) { - final long keyRingRowId = groupCursor.getLong(groupCursor.getColumnIndex(BaseColumns._ID)); - - Cursor fingerprintCursor = getChildCursor(keyRingRowId, CHILD_FINGERPRINT); - Cursor keyCursor = getChildCursor(keyRingRowId, CHILD_KEY); - Cursor userIdCursor = getChildCursor(keyRingRowId, CHILD_USER_ID); - - MergeCursor mergeCursor = new MergeCursor(new Cursor[] { fingerprintCursor, keyCursor, - userIdCursor }); - Log.d(Constants.TAG, "mergeCursor:" + DatabaseUtils.dumpCursorToString(mergeCursor)); - - return mergeCursor; - } - - /** - * This builds a cursor for a specific type of children - * - * @param keyRingRowId - * foreign row id of the keyRing - * @param type - * @return - */ - private Cursor getChildCursor(long keyRingRowId, int type) { - Uri uri = null; - String[] projection = null; - String sortOrder = null; - String selection = null; - - switch (type) { - case CHILD_FINGERPRINT: - projection = new String[] { Keys._ID, Keys.KEY_ID, Keys.IS_MASTER_KEY, Keys.ALGORITHM, - Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN, Keys.CAN_ENCRYPT, }; - sortOrder = Keys.RANK + " ASC"; - - // use only master key for fingerprint - selection = Keys.IS_MASTER_KEY + " = 1 "; - - if (mKeyType == Id.type.public_key) { - uri = Keys.buildPublicKeysUri(String.valueOf(keyRingRowId)); - } else { - uri = Keys.buildSecretKeysUri(String.valueOf(keyRingRowId)); - } - break; - - case CHILD_KEY: - projection = new String[] { Keys._ID, Keys.KEY_ID, Keys.IS_MASTER_KEY, Keys.ALGORITHM, - Keys.KEY_SIZE, Keys.CAN_CERTIFY, Keys.CAN_SIGN, Keys.CAN_ENCRYPT, }; - sortOrder = Keys.RANK + " ASC"; - - if (mKeyType == Id.type.public_key) { - uri = Keys.buildPublicKeysUri(String.valueOf(keyRingRowId)); - } else { - uri = Keys.buildSecretKeysUri(String.valueOf(keyRingRowId)); - } - - break; - - case CHILD_USER_ID: - projection = new String[] { UserIds._ID, UserIds.USER_ID, UserIds.RANK, }; - sortOrder = UserIds.RANK + " ASC"; - - // not the main user id - selection = UserIds.RANK + " > 0 "; - - if (mKeyType == Id.type.public_key) { - uri = UserIds.buildPublicUserIdsUri(String.valueOf(keyRingRowId)); - } else { - uri = UserIds.buildSecretUserIdsUri(String.valueOf(keyRingRowId)); - } - - break; - - default: - return null; - - } - - return mContext.getContentResolver().query(uri, projection, selection, null, sortOrder); - } - -} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java new file mode 100644 index 000000000..f1e58a5d3 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListPublicAdapter.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import java.util.HashMap; +import java.util.Set; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.util.Log; + +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Color; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * Implements StickyListHeadersAdapter from library + */ +public class KeyListPublicAdapter extends CursorAdapter implements StickyListHeadersAdapter { + private LayoutInflater mInflater; + private int mSectionColumnIndex; + + @SuppressLint("UseSparseArrays") + private HashMap mSelection = new HashMap(); + + public KeyListPublicAdapter(Context context, Cursor c, int flags, int sectionColumnIndex) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + mSectionColumnIndex = sectionColumnIndex; + } + + /** + * Bind cursor data to the item list view + * + * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. Thus + * no ViewHolder is required here. + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + int userIdIndex = cursor.getColumnIndex(UserIds.USER_ID); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknown_user_id); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + + String userId = cursor.getString(userIdIndex); + if (userId != null) { + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); + + if (userIdSplit[1] != null) { + mainUserIdRest.setText(userIdSplit[1]); + } + mainUserId.setText(userIdSplit[0]); + } + + if (mainUserId.getText().length() == 0) { + mainUserId.setText(R.string.unknown_user_id); + } + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } else { + mainUserIdRest.setVisibility(View.VISIBLE); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.key_list_item, null); + } + + /** + * Creates a new header view and binds the section headers to it. It uses the ViewHolder + * pattern. Most functionality is similar to getView() from Android's CursorAdapter. + * + * NOTE: The variables mDataValid and mCursor are available due to the super class + * CursorAdapter. + */ + @Override + public View getHeaderView(int position, View convertView, ViewGroup parent) { + HeaderViewHolder holder; + if (convertView == null) { + holder = new HeaderViewHolder(); + convertView = mInflater.inflate(R.layout.key_list_public_header, parent, false); + holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); + convertView.setTag(holder); + } else { + holder = (HeaderViewHolder) convertView.getTag(); + } + + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return convertView; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + // set header text as first char in user id + String headerText = "" + mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0); + holder.text.setText(headerText); + return convertView; + } + + /** + * Header IDs should be static, position=1 should always return the same Id that is. + */ + @Override + public long getHeaderId(int position) { + if (!mDataValid) { + // no data available at this point + Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); + return -1; + } + + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("couldn't move cursor to position " + position); + } + + // return the first character of the name as ID because this is what + // headers private HashMap mSelection = new HashMap();are based upon + return mCursor.getString(mSectionColumnIndex).subSequence(0, 1).charAt(0); + } + + class HeaderViewHolder { + TextView text; + } + + /** -------------------------- MULTI-SELECTION METHODS -------------- */ + public void setNewSelection(int position, boolean value) { + mSelection.put(position, value); + notifyDataSetChanged(); + } + + public boolean isPositionChecked(int position) { + Boolean result = mSelection.get(position); + return result == null ? false : result; + } + + public Set getCurrentCheckedPosition() { + return mSelection.keySet(); + } + + public void removeSelection(int position) { + mSelection.remove(position); + notifyDataSetChanged(); + } + + public void clearSelection() { + mSelection.clear(); + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // let the adapter handle setting up the row views + View v = super.getView(position, convertView, parent); + + /** + * Change color for multi-selection + */ + // default color + v.setBackgroundColor(Color.TRANSPARENT); + if (mSelection.get(position) != null) { + // this is a selected position, change color! + v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + } + return v; + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java new file mode 100644 index 000000000..d06c0287c --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/KeyListSecretAdapter.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import java.util.HashMap; +import java.util.Set; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Color; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class KeyListSecretAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + @SuppressLint("UseSparseArrays") + private HashMap mSelection = new HashMap(); + + public KeyListSecretAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + int userIdIndex = cursor.getColumnIndex(UserIds.USER_ID); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknown_user_id); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + + String userId = cursor.getString(userIdIndex); + if (userId != null) { + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); + + if (userIdSplit[1] != null) { + mainUserIdRest.setText(userIdSplit[1]); + } + mainUserId.setText(userIdSplit[0]); + } + + if (mainUserId.getText().length() == 0) { + mainUserId.setText(R.string.unknown_user_id); + } + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } else { + mainUserIdRest.setVisibility(View.VISIBLE); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.key_list_item, null); + } + + /** -------------------------- MULTI-SELECTION METHODS -------------- */ + public void setNewSelection(int position, boolean value) { + mSelection.put(position, value); + notifyDataSetChanged(); + } + + public boolean isPositionChecked(int position) { + Boolean result = mSelection.get(position); + return result == null ? false : result; + } + + public Set getCurrentCheckedPosition() { + return mSelection.keySet(); + } + + public void removeSelection(int position) { + mSelection.remove(position); + notifyDataSetChanged(); + } + + public void clearSelection() { + mSelection.clear(); + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // let the adapter handle setting up the row views + View v = super.getView(position, convertView, parent); + + /** + * Change color for multi-selection + */ + // default color + v.setBackgroundColor(Color.TRANSPARENT); + if (mSelection.get(position) != null) { + // this is a selected position, change color! + v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + } + return v; + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index ebb7261be..c6eca0296 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -18,11 +18,10 @@ package org.sufficientlysecure.keychain.ui.adapter; import org.sufficientlysecure.keychain.Id; -import org.sufficientlysecure.keychain.helper.OtherHelper; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; -import org.sufficientlysecure.keychain.R; import android.content.Context; import android.database.Cursor; @@ -78,7 +77,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter { String userId = cursor.getString(cursor.getColumnIndex(UserIds.USER_ID)); if (userId != null) { - String[] userIdSplit = OtherHelper.splitUserId(userId); + String[] userIdSplit = PgpKeyHelper.splitUserId(userId); if (userIdSplit[1] != null) { mainUserIdRest.setText(userIdSplit[1]); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java new file mode 100644 index 000000000..51286af66 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyKeysAdapter.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +public class ViewKeyKeysAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + public ViewKeyKeysAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(cursor + .getColumnIndex(Keys.KEY_ID))); + String algorithmStr = PgpKeyHelper.getAlgorithmInfo( + cursor.getInt(cursor.getColumnIndex(Keys.ALGORITHM)), + cursor.getInt(cursor.getColumnIndex(Keys.KEY_SIZE))); + + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(keyIdStr); + + TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + keyDetails.setText("(" + algorithmStr + ")"); + + ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey); + if (cursor.getInt(cursor.getColumnIndex(Keys.IS_MASTER_KEY)) != 1) { + masterKeyIcon.setVisibility(View.INVISIBLE); + } else { + masterKeyIcon.setVisibility(View.VISIBLE); + } + + ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey); + if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_CERTIFY)) != 1) { + certifyIcon.setVisibility(View.GONE); + } else { + certifyIcon.setVisibility(View.VISIBLE); + } + + ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); + if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_ENCRYPT)) != 1) { + encryptIcon.setVisibility(View.GONE); + } else { + encryptIcon.setVisibility(View.VISIBLE); + } + + ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + if (cursor.getInt(cursor.getColumnIndex(Keys.CAN_SIGN)) != 1) { + signIcon.setVisibility(View.GONE); + } else { + signIcon.setVisibility(View.VISIBLE); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.view_key_keys_item, null); + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java new file mode 100644 index 000000000..2e2606fd0 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.adapter; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.widget.CursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class ViewKeyUserIdsAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + int userIdIndex = cursor.getColumnIndex(UserIds.USER_ID); + + String userIdStr = cursor.getString(userIdIndex); + + TextView userId = (TextView) view.findViewById(R.id.userId); + userId.setText(userIdStr); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.view_key_userids_item, null); + } + +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java index 638702b57..101167777 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java @@ -38,7 +38,7 @@ import android.support.v4.app.FragmentActivity; public class DeleteKeyDialogFragment extends DialogFragment { private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_DELETE_KEY_RING_ROW_ID = "delete_file"; + private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file"; private static final String ARG_KEY_TYPE = "key_type"; public static final int MESSAGE_OKAY = 1; @@ -48,13 +48,13 @@ public class DeleteKeyDialogFragment extends DialogFragment { /** * Creates new instance of this delete file dialog fragment */ - public static DeleteKeyDialogFragment newInstance(Messenger messenger, long deleteKeyRingRowId, + public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds, int keyType) { DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); - args.putLong(ARG_DELETE_KEY_RING_ROW_ID, deleteKeyRingRowId); + args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds); args.putInt(ARG_KEY_TYPE, keyType); frag.setArguments(args); @@ -70,36 +70,48 @@ public class DeleteKeyDialogFragment extends DialogFragment { final FragmentActivity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); - final long deleteKeyRingRowId = getArguments().getLong(ARG_DELETE_KEY_RING_ROW_ID); + final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS); final int keyType = getArguments().getInt(ARG_KEY_TYPE); - // TODO: better way to do this? - String userId = activity.getString(R.string.unknown_user_id); - - if (keyType == Id.type.public_key) { - PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity, - deleteKeyRingRowId); - userId = PgpKeyHelper.getMainUserIdSafe(activity, PgpKeyHelper.getMasterKey(keyRing)); - } else { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity, - deleteKeyRingRowId); - userId = PgpKeyHelper.getMainUserIdSafe(activity, PgpKeyHelper.getMasterKey(keyRing)); - } - AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.warning); - builder.setMessage(getString( - keyType == Id.type.public_key ? R.string.key_deletion_confirmation - : R.string.secret_key_deletion_confirmation, userId)); + + if (keyRingRowIds.length == 1) { + // TODO: better way to do this? + String userId = activity.getString(R.string.unknown_user_id); + + if (keyType == Id.type.public_key) { + PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByRowId(activity, + keyRingRowIds[0]); + userId = PgpKeyHelper.getMainUserIdSafe(activity, + PgpKeyHelper.getMasterKey(keyRing)); + } else { + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByRowId(activity, + keyRingRowIds[0]); + userId = PgpKeyHelper.getMainUserIdSafe(activity, + PgpKeyHelper.getMasterKey(keyRing)); + } + + builder.setMessage(getString( + keyType == Id.type.public_key ? R.string.key_deletion_confirmation + : R.string.secret_key_deletion_confirmation, userId)); + } else { + builder.setMessage(R.string.key_deletion_confirmation_multi); + } + builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { if (keyType == Id.type.public_key) { - ProviderHelper.deletePublicKeyRing(activity, deleteKeyRingRowId); + for (long keyRowId : keyRingRowIds) { + ProviderHelper.deletePublicKeyRing(activity, keyRowId); + } } else { - ProviderHelper.deleteSecretKeyRing(activity, deleteKeyRingRowId); + for (long keyRowId : keyRingRowIds) { + ProviderHelper.deleteSecretKeyRing(activity, keyRowId); + } } dismiss(); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 730fcc520..e65da2aa8 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -18,9 +18,9 @@ package org.sufficientlysecure.keychain.ui.dialog; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.FileHelper; import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.R; import android.app.Activity; import android.app.AlertDialog; @@ -36,7 +36,8 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; -import android.widget.ImageButton; + +import com.beardedhen.androidbootstrap.BootstrapButton; // TODO: return result from file manager activity to this dialog! not the activity! // do it like in ImportFileFragment! @@ -55,6 +56,10 @@ public class FileDialogFragment extends DialogFragment { private Messenger mMessenger; + private EditText mFilename; + private BootstrapButton mBrowse; + private CheckBox mCheckBox; + /** * Creates new instance of this file dialog fragment */ @@ -90,10 +95,6 @@ public class FileDialogFragment extends DialogFragment { String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT); final int requestCode = getArguments().getInt(ARG_REQUEST_CODE); - final EditText mFilename; - final ImageButton mBrowse; - final CheckBox mCheckBox; - LayoutInflater inflater = (LayoutInflater) activity .getSystemService(Context.LAYOUT_INFLATER_SERVICE); AlertDialog.Builder alert = new AlertDialog.Builder(activity); @@ -105,13 +106,13 @@ public class FileDialogFragment extends DialogFragment { mFilename = (EditText) view.findViewById(R.id.input); mFilename.setText(defaultFile); - mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); + mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // only .asc or .gpg files - // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc or gpg types! - FileHelper.openFile(activity, mFilename.getText().toString(), "*/*", - requestCode); + // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc + // or gpg types! + FileHelper.openFile(activity, mFilename.getText().toString(), "*/*", requestCode); } }); @@ -196,4 +197,3 @@ public class FileDialogFragment extends DialogFragment { } } } - diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index aba7e974e..e88271240 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -141,7 +141,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor } LayoutInflater inflater = activity.getLayoutInflater(); - View view = inflater.inflate(R.layout.passphrase, null); + View view = inflater.inflate(R.layout.passphrase_dialog, null); alert.setView(view); mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index 797b28829..d5c366bed 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -90,7 +90,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi alert.setMessage(R.string.enter_passphrase_twice); LayoutInflater inflater = activity.getLayoutInflater(); - View view = inflater.inflate(R.layout.passphrase_repeat, null); + View view = inflater.inflate(R.layout.passphrase_repeat_dialog, null); alert.setView(view); mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java new file mode 100644 index 000000000..03e09cdcb --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/ShareNfcDialogFragment.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import org.sufficientlysecure.htmltextview.HtmlTextView; +import org.sufficientlysecure.keychain.R; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.nfc.NfcAdapter; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public class ShareNfcDialogFragment extends DialogFragment { + + /** + * Creates new instance of this fragment + */ + public static ShareNfcDialogFragment newInstance() { + ShareNfcDialogFragment frag = new ShareNfcDialogFragment(); + + return frag; + } + + /** + * Creates dialog + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + + AlertDialog.Builder alert = new AlertDialog.Builder(activity); + + alert.setIcon(android.R.drawable.ic_dialog_info); + alert.setTitle(R.string.share_nfc_dialog); + alert.setCancelable(true); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dismiss(); + } + }); + + HtmlTextView textView = new HtmlTextView(getActivity()); + textView.setPadding(8, 8, 8, 8); + alert.setView(textView); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + textView.setText(getString(R.string.error) + ": " + + getString(R.string.error_jelly_bean_needed)); + } else { + // check if NFC Adapter is available + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity()); + if (nfcAdapter == null) { + textView.setText(getString(R.string.error) + ": " + + getString(R.string.error_nfc_needed)); + } else { + // nfc works... + textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share); + + alert.setNegativeButton(R.string.menu_beam_preferences, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + Intent intentSettings = new Intent( + Settings.ACTION_NFCSHARING_SETTINGS); + startActivity(intentSettings); + } + }); + } + } + + // no flickering when clicking textview for Android < 4 + // aboutTextView.setTextColor(getResources().getColor(android.R.color.black)); + + return alert.create(); + } +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/DashboardLayout.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/DashboardLayout.java deleted file mode 100644 index 158a271bc..000000000 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/DashboardLayout.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.sufficientlysecure.keychain.ui.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -/** - * Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and - * vertical whitespace. - */ -public class DashboardLayout extends ViewGroup { - private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10; - - private int mMaxChildWidth = 0; - private int mMaxChildHeight = 0; - - public DashboardLayout(Context context) { - super(context, null); - } - - public DashboardLayout(Context context, AttributeSet attrs) { - super(context, attrs, 0); - } - - public DashboardLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mMaxChildWidth = 0; - mMaxChildHeight = 0; - - // Measure once to find the maximum child size. - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST); - int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST); - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - - mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth()); - mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight()); - } - - // Measure again for each child to be exactly the same size. - - childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY); - childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY); - - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec), - resolveSize(mMaxChildHeight, heightMeasureSpec)); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int width = r - l; - int height = b - t; - - final int count = getChildCount(); - - // Calculate the number of visible children. - int visibleCount = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - ++visibleCount; - } - - if (visibleCount == 0) { - return; - } - - // Calculate what number of rows and columns will optimize for even horizontal and - // vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on. - int bestSpaceDifference = Integer.MAX_VALUE; - int spaceDifference; - - // Horizontal and vertical space between items - int hSpace = 0; - int vSpace = 0; - - int cols = 1; - int rows; - - while (true) { - rows = (visibleCount - 1) / cols + 1; - - hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); - vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); - - spaceDifference = Math.abs(vSpace - hSpace); - if (rows * cols != visibleCount) { - spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; - } else if (rows * mMaxChildHeight > height || cols * mMaxChildWidth > width) { - spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; - } - - if (spaceDifference < bestSpaceDifference) { - // Found a better whitespace squareness/ratio - bestSpaceDifference = spaceDifference; - - // If we found a better whitespace squareness and there's only 1 row, this is - // the best we can do. - if (rows == 1) { - break; - } - } else { - // This is a worse whitespace ratio, use the previous value of cols and exit. - --cols; - rows = (visibleCount - 1) / cols + 1; - hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); - vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); - break; - } - - ++cols; - } - - // Lay out children based on calculated best-fit number of rows and cols. - - // If we chose a layout that has negative horizontal or vertical space, force it to zero. - hSpace = Math.max(0, hSpace); - vSpace = Math.max(0, vSpace); - - // Re-use width/height variables to be child width/height. - width = (width - hSpace * (cols + 1)) / cols; - height = (height - vSpace * (rows + 1)) / rows; - - int left, top; - int col, row; - int visibleIndex = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - row = visibleIndex / cols; - col = visibleIndex % cols; - - left = hSpace * (col + 1) + width * col; - top = vSpace * (row + 1) + height * row; - - child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width), - (vSpace == 0 && row == rows - 1) ? b : (top + height)); - ++visibleIndex; - } - } -} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/FixedListView.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/FixedListView.java new file mode 100644 index 000000000..277f14b6f --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/FixedListView.java @@ -0,0 +1,55 @@ +package org.sufficientlysecure.keychain.ui.widget; + +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + +/** + * Automatically calculate height of listview based on contained items. This enables to put this + * ListView into a ScrollView without messing up. + * + * from + * http://stackoverflow.com/questions/2419246/how-do-i-create-a-listview-thats-not-in-a-scrollview- + * or-has-the-scrollview-dis + */ +public class FixedListView extends ListView { + + public FixedListView(Context context) { + super(context); + } + + public FixedListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public FixedListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Calculate height of the entire list by providing a very large + // height hint. But do not use the highest 2 bits of this integer; + // those are reserved for the MeasureSpec mode. + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } + +} \ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java index 5748839bc..0f5d26644 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyEditor.java @@ -16,12 +16,18 @@ package org.sufficientlysecure.keychain.ui.widget; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Vector; + import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPSecretKey; import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.util.Choice; -import org.sufficientlysecure.keychain.R; import android.app.DatePickerDialog; import android.app.Dialog; @@ -32,18 +38,12 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.Button; import android.widget.DatePicker; -import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Vector; +import com.beardedhen.androidbootstrap.BootstrapButton; public class KeyEditor extends LinearLayout implements Editor, OnClickListener { private PGPSecretKey mKey; @@ -51,12 +51,12 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; private boolean mIsMasterKey; - ImageButton mDeleteButton; + BootstrapButton mDeleteButton; TextView mAlgorithm; TextView mKeyId; Spinner mUsage; TextView mCreationDate; - Button mExpiryDateButton; + BootstrapButton mExpiryDateButton; GregorianCalendar mExpiryDate; private int mDatePickerResultCount = 0; @@ -87,7 +87,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { mAlgorithm = (TextView) findViewById(R.id.algorithm); mKeyId = (TextView) findViewById(R.id.keyId); mCreationDate = (TextView) findViewById(R.id.creation); - mExpiryDateButton = (Button) findViewById(R.id.expiry); + mExpiryDateButton = (BootstrapButton) findViewById(R.id.expiry); mUsage = (Spinner) findViewById(R.id.usage); Choice choices[] = { new Choice(Id.choice.usage.sign_only, getResources().getString( @@ -101,7 +101,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mUsage.setAdapter(adapter); - mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton = (BootstrapButton) findViewById(R.id.delete); mDeleteButton.setOnClickListener(this); setExpiryDate(null); @@ -118,16 +118,18 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { date.get(Calendar.DAY_OF_MONTH)); mDatePickerResultCount = 0; dialog.setCancelable(true); - dialog.setButton(Dialog.BUTTON_NEGATIVE, getContext() - .getString(R.string.btn_no_date), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (mDatePickerResultCount++ == 0) // Note: Ignore results after the first - // one - android sends multiples. - { - setExpiryDate(null); - } - } - }); + dialog.setButton(Dialog.BUTTON_NEGATIVE, + getContext().getString(R.string.btn_no_date), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mDatePickerResultCount++ == 0) // Note: Ignore results after the + // first + // one - android sends multiples. + { + setExpiryDate(null); + } + } + }); dialog.show(); } }); @@ -237,7 +239,7 @@ public class KeyEditor extends LinearLayout implements Editor, OnClickListener { private void setExpiryDate(GregorianCalendar date) { mExpiryDate = date; if (date == null) { - mExpiryDateButton.setText(R.string.none); + mExpiryDateButton.setText(getContext().getString(R.string.none)); } else { mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); } diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java index 5fdb2c9c8..01259ccd1 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java @@ -23,14 +23,15 @@ import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import com.beardedhen.androidbootstrap.BootstrapButton; + public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; - ImageButton mDeleteButton; + BootstrapButton mDeleteButton; TextView mServer; public KeyServerEditor(Context context) { @@ -48,7 +49,7 @@ public class KeyServerEditor extends LinearLayout implements Editor, OnClickList mServer = (TextView) findViewById(R.id.server); - mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton = (BootstrapButton) findViewById(R.id.delete); mDeleteButton.setOnClickListener(this); super.onFinishInflate(); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java index 99190e9c7..b33dbe4c5 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -45,17 +45,16 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.beardedhen.androidbootstrap.BootstrapButton; public class SectionView extends LinearLayout implements OnClickListener, EditorListener { private LayoutInflater mInflater; - private View mAdd; - private ImageView mPlusButton; + private BootstrapButton mPlusButton; private ViewGroup mEditors; private TextView mTitle; private int mType = 0; @@ -103,7 +102,6 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor public void setCanEdit(boolean bCanEdit) { canEdit = bCanEdit; - mPlusButton = (ImageView) findViewById(R.id.plusbutton); if (!canEdit) { mPlusButton.setVisibility(View.INVISIBLE); } @@ -117,8 +115,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor setDrawingCacheEnabled(true); setAlwaysDrawnWithCacheEnabled(true); - mAdd = findViewById(R.id.header); - mAdd.setOnClickListener(this); + mPlusButton = (BootstrapButton) findViewById(R.id.plusbutton); + mPlusButton.setOnClickListener(this); mEditors = (ViewGroup) findViewById(R.id.editors); mTitle = (TextView) findViewById(R.id.title); @@ -155,7 +153,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor case Id.type.key: { AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); - View view = mInflater.inflate(R.layout.create_key, null); + View view = mInflater.inflate(R.layout.create_key_dialog, null); dialog.setView(view); dialog.setTitle(R.string.title_create_key); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java new file mode 100644 index 000000000..752d44f89 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UnderlineTextView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 Eric Frohnhoefer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * Copied from StickyListHeaders lib example + * + * @author Eric Frohnhoefer + */ +public class UnderlineTextView extends TextView { + private final Paint mPaint = new Paint(); + private int mUnderlineHeight = 0; + + public UnderlineTextView(Context context) { + this(context, null); + } + + public UnderlineTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public UnderlineTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + Resources r = getResources(); + mUnderlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, + r.getDisplayMetrics()); + } + + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom + mUnderlineHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Draw the underline the same color as the text + mPaint.setColor(getTextColors().getDefaultColor()); + canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint); + } +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java index 6c65e840c..5428b626e 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/UserIdEditor.java @@ -27,14 +27,15 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.EditText; -import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RadioButton; +import com.beardedhen.androidbootstrap.BootstrapButton; + public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { private EditorListener mEditorListener = null; - private ImageButton mDeleteButton; + private BootstrapButton mDeleteButton; private RadioButton mIsMainUserId; private EditText mName; private EditText mEmail; @@ -95,7 +96,7 @@ public class UserIdEditor extends LinearLayout implements Editor, OnClickListene setDrawingCacheEnabled(true); setAlwaysDrawnWithCacheEnabled(true); - mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton = (BootstrapButton) findViewById(R.id.delete); mDeleteButton.setOnClickListener(this); mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); mIsMainUserId.setOnClickListener(this); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/SectionCursorAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/SectionCursorAdapter.java new file mode 100644 index 000000000..0d4092d9e --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/SectionCursorAdapter.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * Copyright (C) 2011 Gonçalo Ferreira + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.util; + +import java.util.LinkedHashMap; + +import android.content.Context; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.support.v4.widget.CursorAdapter; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * Originally from https://github.com/monxalo/android-section-adapter + * + * getCustomGroup() has been modified + */ +public abstract class SectionCursorAdapter extends CursorAdapter { + + private static final String TAG = "SectionCursorAdapter"; + private static final boolean LOGV = false; + + private static final int TYPE_NORMAL = 1; + private static final int TYPE_HEADER = 0; + private static final int TYPE_COUNT = 2; + + private final int mHeaderRes; + private final int mGroupColumn; + private final LayoutInflater mLayoutInflater; + + private LinkedHashMap sectionsIndexer; + + public static class ViewHolder { + public TextView textView; + } + + public SectionCursorAdapter(Context context, Cursor c, int headerLayout, int groupColumn) { + super(context, c, 0); + + sectionsIndexer = new LinkedHashMap(); + + mHeaderRes = headerLayout; + mGroupColumn = groupColumn; + mLayoutInflater = LayoutInflater.from(context); + + if (c != null) { + calculateSectionHeaders(); + c.registerDataSetObserver(mDataSetObserver); + } + } + + private DataSetObserver mDataSetObserver = new DataSetObserver() { + public void onChanged() { + calculateSectionHeaders(); + }; + + public void onInvalidated() { + sectionsIndexer.clear(); + }; + }; + + /** + *

    + * This method serve as an intercepter before the sections are calculated so you can transform + * some computer data into human readable, e.g. format a unix timestamp, or a status. + *

    + * + *

    + * By default this method returns the original data for the group column. + *

    + * + * @param groupData + * @return + */ + protected String getCustomGroup(String groupData) { + return groupData.substring(0, 1); + } + + private void calculateSectionHeaders() { + int i = 0; + + String previous = ""; + int count = 0; + + final Cursor c = getCursor(); + + sectionsIndexer.clear(); + + if (c == null) { + return; + } + + c.moveToPosition(-1); + + while (c.moveToNext()) { + final String group = getCustomGroup(c.getString(mGroupColumn)); + + if (!previous.equals(group)) { + sectionsIndexer.put(i + count, group); + previous = group; + + if (LOGV) + Log.v(TAG, "Group " + group + "at position: " + (i + count)); + + count++; + } + + i++; + } + } + + public String getGroupCustomFormat(Object obj) { + return null; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + int viewType = getItemViewType(position); + + if (viewType == TYPE_NORMAL) { + Cursor c = (Cursor) getItem(position); + + if (c == null) { + if (LOGV) + Log.d(TAG, "getItem(" + position + ") = null"); + return mLayoutInflater.inflate(mHeaderRes, parent, false); + } + + final int mapCursorPos = getSectionForPosition(position); + c.moveToPosition(mapCursorPos); + + return super.getView(mapCursorPos, convertView, parent); + } else { + ViewHolder holder = null; + + if (convertView == null) { + if (LOGV) + Log.v(TAG, "Creating new view for section"); + + holder = new ViewHolder(); + convertView = mLayoutInflater.inflate(mHeaderRes, parent, false); + holder.textView = (TextView) convertView; + + convertView.setTag(holder); + } else { + if (LOGV) + Log.v(TAG, "Reusing view for section"); + + holder = (ViewHolder) convertView.getTag(); + } + + TextView sectionText = holder.textView; + + final String group = sectionsIndexer.get(position); + final String customFormat = getGroupCustomFormat(group); + + sectionText.setText(customFormat == null ? group : customFormat); + + return sectionText; + } + } + + @Override + public int getViewTypeCount() { + return TYPE_COUNT; + } + + @Override + public int getCount() { + return super.getCount() + sectionsIndexer.size(); + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) == TYPE_NORMAL; + } + + public int getPositionForSection(int section) { + if (sectionsIndexer.containsKey(section)) { + return section + 1; + } + return section; + } + + public int getSectionForPosition(int position) { + int offset = 0; + for (Integer key : sectionsIndexer.keySet()) { + if (position > key) { + offset++; + } else { + break; + } + } + + return position - offset; + } + + @Override + public Object getItem(int position) { + if (getItemViewType(position) == TYPE_NORMAL) { + return super.getItem(getSectionForPosition(position)); + } + return super.getItem(position); + } + + @Override + public long getItemId(int position) { + if (getItemViewType(position) == TYPE_NORMAL) { + return super.getItemId(getSectionForPosition(position)); + } + return super.getItemId(position); + } + + @Override + public int getItemViewType(int position) { + if (position == getPositionForSection(position)) { + return TYPE_NORMAL; + } + return TYPE_HEADER; + } + + @Override + public void changeCursor(Cursor cursor) { + final Cursor old = swapCursor(cursor); + + if (old != null) { + old.close(); + } + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + if (getCursor() != null) { + getCursor().unregisterDataSetObserver(mDataSetObserver); + } + + final Cursor oldCursor = super.swapCursor(newCursor); + + calculateSectionHeaders(); + + if (newCursor != null) { + newCursor.registerDataSetObserver(mDataSetObserver); + } + + return oldCursor; + } +} \ No newline at end of file diff --git a/README.md b/README.md index 601d4710c..df2e72850 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ Translations are managed at Transifex, please contribute there at https://www.tr ## Code Contributions -Fork OpenPGP Keychain and do a pull request. I will help with occuring problems and merge your changes back into the main project. +Fork OpenPGP Keychain and create a pull request. I will help with occuring problems and merge your changes back into the main project. I am happy about every code contribution and appreciate your effort to help us developing OpenPGP Keychain :) ## Build with Gradle 1. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html) -2. Export ANDROID_HOME pointing to your Android SDK -3. Download Android Support Repository, and Google Repository using Android SDK Manager +2. Open the Android SDK Manager (shell command: ``android``). Expand the Extras directory and install "Android Support Repository" +3. Export ANDROID_HOME pointing to your Android SDK 4. Execute ``./gradlew build`` ## Development with Eclipse @@ -27,9 +27,11 @@ Android Studio is currently not supported or recommended! 1. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/ActionBarSherlock" 2. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/HtmlTextView" -3. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/pinned-section-listview/library" -4. File -> Import -> Android -> Existing Android Code Into Workspace, choose "OpenPGP-Keychain" -5. OpenPGP-Kechain can now be build +3. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/StickyListHeaders/library" +4. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/zxing" +5. File -> Import -> Android -> Existing Android Code Into Workspace, choose "libraries/AndroidBootstrap" +6. File -> Import -> Android -> Existing Android Code Into Workspace, choose "OpenPGP-Keychain" +7. OpenPGP-Kechain can now be build # Keychain API @@ -107,13 +109,6 @@ TODO # Libraries -All JAR-Libraries are provided in this repository under "libs", all Android Library projects are under "libraries". - -* ActionBarSherlock to provide an ActionBar for Android < 3.0 -* HtmlTextView for non-crashing TextViews with HTML content -* forked Spongy Castle Crypto Lib (Android version of Bouncy Castle) -* barcodescanner-android-integration-supportv4.jar: Barcode Scanner Integration - ## Build Barcode Scanner Integration 1. Checkout their SVN (see http://code.google.com/p/zxing/source/checkout) @@ -124,6 +119,10 @@ All JAR-Libraries are provided in this repository under "libs", all Android Libr On error see: http://code.google.com/p/zxing/issues/detail?id=1207 +## ZXing + +ZXing classes were extracted from the ZXing library (http://code.google.com/p/zxing/). +Only classes related to QR Code generation are utilized. ## Bouncy Castle @@ -208,13 +207,17 @@ Some parts (older parts and some libraries are Apache License v2, MIT X11 Licens https://github.com/dschuermann/html-textview Apache License v2 -* ZXing QRCode Integration +* ZXing http://code.google.com/p/zxing/ Apache License v2 -* pinned-section-listview - https://github.com/beworker/pinned-section-listview +* StickyListHeaders + https://github.com/emilsjolander/StickyListHeaders Apache License v2 + +* Android-Bootstrap + https://github.com/Bearded-Hen/Android-Bootstrap + MIT License ## Images diff --git a/libraries/ActionBarSherlock/build.gradle b/libraries/ActionBarSherlock/build.gradle index 41bbe1d9b..e25786733 100644 --- a/libraries/ActionBarSherlock/build.gradle +++ b/libraries/ActionBarSherlock/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'android-library' dependencies { - compile 'com.android.support:support-v4:18.0.+' + compile 'com.android.support:support-v4:19.0.+' } android { diff --git a/libraries/ActionBarSherlock/libs/android-support-v4.jar b/libraries/ActionBarSherlock/libs/android-support-v4.jar index 99e063b33..9056828a0 100644 Binary files a/libraries/ActionBarSherlock/libs/android-support-v4.jar and b/libraries/ActionBarSherlock/libs/android-support-v4.jar differ diff --git a/libraries/AndroidBootstrap/AndroidManifest.xml b/libraries/AndroidBootstrap/AndroidManifest.xml new file mode 100644 index 000000000..ee1013229 --- /dev/null +++ b/libraries/AndroidBootstrap/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/libraries/AndroidBootstrap/build.gradle b/libraries/AndroidBootstrap/build.gradle new file mode 100644 index 000000000..a63f9e02c --- /dev/null +++ b/libraries/AndroidBootstrap/build.gradle @@ -0,0 +1,17 @@ +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion "19.0.0" + defaultConfig { + minSdkVersion 7 + targetSdkVersion 17 + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/libraries/AndroidBootstrap/libs/android-support-v4.jar b/libraries/AndroidBootstrap/libs/android-support-v4.jar new file mode 100644 index 000000000..9056828a0 Binary files /dev/null and b/libraries/AndroidBootstrap/libs/android-support-v4.jar differ diff --git a/libraries/AndroidBootstrap/proguard-project.txt b/libraries/AndroidBootstrap/proguard-project.txt new file mode 100644 index 000000000..f2fe1559a --- /dev/null +++ b/libraries/AndroidBootstrap/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/libraries/AndroidBootstrap/project.properties b/libraries/AndroidBootstrap/project.properties new file mode 100644 index 000000000..1b8c5a340 --- /dev/null +++ b/libraries/AndroidBootstrap/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-18 +android.library=true diff --git a/libraries/AndroidBootstrap/res/drawable-hdpi/ic_launcher.png b/libraries/AndroidBootstrap/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/libraries/AndroidBootstrap/res/drawable-hdpi/ic_launcher.png differ diff --git a/libraries/AndroidBootstrap/res/drawable-mdpi/ic_launcher.png b/libraries/AndroidBootstrap/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/libraries/AndroidBootstrap/res/drawable-mdpi/ic_launcher.png differ diff --git a/libraries/AndroidBootstrap/res/drawable-xhdpi/ic_launcher.png b/libraries/AndroidBootstrap/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/libraries/AndroidBootstrap/res/drawable-xhdpi/ic_launcher.png differ diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_danger.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_danger.xml new file mode 100644 index 000000000..4fd748cb4 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_danger.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_danger_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_danger_rounded.xml new file mode 100644 index 000000000..2fe8b2571 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_danger_rounded.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_default.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_default.xml new file mode 100644 index 000000000..77318eae0 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_default.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_default_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_default_rounded.xml new file mode 100644 index 000000000..923a2b66f --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_default_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_info.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_info.xml new file mode 100644 index 000000000..5727e095e --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_info.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_info_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_info_rounded.xml new file mode 100644 index 000000000..c171215ef --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_info_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_inverse.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_inverse.xml new file mode 100644 index 000000000..bee362b30 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_inverse.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_inverse_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_inverse_rounded.xml new file mode 100644 index 000000000..e5ceb1da8 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_inverse_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_primary.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_primary.xml new file mode 100644 index 000000000..5e438f37d --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_primary.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_primary_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_primary_rounded.xml new file mode 100644 index 000000000..88d08ea5d --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_primary_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_success.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_success.xml new file mode 100644 index 000000000..c611ae9ba --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_success.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_success_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_success_rounded.xml new file mode 100644 index 000000000..5536ac0fd --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_success_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_warning.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_warning.xml new file mode 100644 index 000000000..e0596201b --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_warning.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bbuton_warning_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bbuton_warning_rounded.xml new file mode 100644 index 000000000..ef0220d60 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bbuton_warning_rounded.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_rounded.xml b/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_rounded.xml new file mode 100644 index 000000000..01d8c00e2 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_rounded.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_square.xml b/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_square.xml new file mode 100644 index 000000000..2c2729085 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bthumbnail_container_square.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/bthumbnail_placeholder_default.xml b/libraries/AndroidBootstrap/res/drawable/bthumbnail_placeholder_default.xml new file mode 100644 index 000000000..fa0013790 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/bthumbnail_placeholder_default.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background.xml new file mode 100644 index 000000000..f7f58502a --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_danger.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_danger.xml new file mode 100644 index 000000000..dd38089d0 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_danger.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded.xml new file mode 100644 index 000000000..d3a318fd8 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_danger.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_danger.xml new file mode 100644 index 000000000..ad2d03a5e --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_danger.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_success.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_success.xml new file mode 100644 index 000000000..7ef80a5f5 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_success.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_warning.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_warning.xml new file mode 100644 index 000000000..b90c3f96f --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_rounded_warning.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_success.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_success.xml new file mode 100644 index 000000000..8f6af9700 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_success.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/edittext_background_warning.xml b/libraries/AndroidBootstrap/res/drawable/edittext_background_warning.xml new file mode 100644 index 000000000..0f95154c9 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/edittext_background_warning.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/thumbnail_circle.xml b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle.xml new file mode 100644 index 000000000..c8d3ab2ff --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_container.xml b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_container.xml new file mode 100644 index 000000000..7f9e90d84 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_container.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_minimal.xml b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_minimal.xml new file mode 100644 index 000000000..80d4c41b4 --- /dev/null +++ b/libraries/AndroidBootstrap/res/drawable/thumbnail_circle_minimal.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/layout/bootstrap_button.xml b/libraries/AndroidBootstrap/res/layout/bootstrap_button.xml new file mode 100644 index 000000000..d6260eaba --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/bootstrap_button.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/layout/bootstrap_button_fill.xml b/libraries/AndroidBootstrap/res/layout/bootstrap_button_fill.xml new file mode 100644 index 000000000..545f9b68a --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/bootstrap_button_fill.xml @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/layout/bootstrap_edit_text.xml b/libraries/AndroidBootstrap/res/layout/bootstrap_edit_text.xml new file mode 100644 index 000000000..63b5b007a --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/bootstrap_edit_text.xml @@ -0,0 +1,14 @@ + + + + diff --git a/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail.xml b/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail.xml new file mode 100644 index 000000000..fb3b43042 --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail_circle.xml b/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail_circle.xml new file mode 100644 index 000000000..2e4394160 --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/bootstrap_thumbnail_circle.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/layout/font_awesome_text.xml b/libraries/AndroidBootstrap/res/layout/font_awesome_text.xml new file mode 100644 index 000000000..3fd7aeebc --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/font_awesome_text.xml @@ -0,0 +1,14 @@ + + + + diff --git a/libraries/AndroidBootstrap/res/layout/row_title.xml b/libraries/AndroidBootstrap/res/layout/row_title.xml new file mode 100644 index 000000000..dd5a3573a --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/row_title.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/layout/row_title_and_subtitle.xml b/libraries/AndroidBootstrap/res/layout/row_title_and_subtitle.xml new file mode 100644 index 000000000..ec25c0a46 --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/row_title_and_subtitle.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/layout/row_two_columns.xml b/libraries/AndroidBootstrap/res/layout/row_two_columns.xml new file mode 100644 index 000000000..1029bffb5 --- /dev/null +++ b/libraries/AndroidBootstrap/res/layout/row_two_columns.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/AndroidBootstrap/res/menu/main.xml b/libraries/AndroidBootstrap/res/menu/main.xml new file mode 100644 index 000000000..c00202823 --- /dev/null +++ b/libraries/AndroidBootstrap/res/menu/main.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/libraries/AndroidBootstrap/res/values-sw600dp/dimens.xml b/libraries/AndroidBootstrap/res/values-sw600dp/dimens.xml new file mode 100644 index 000000000..44f01db75 --- /dev/null +++ b/libraries/AndroidBootstrap/res/values-sw600dp/dimens.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/libraries/AndroidBootstrap/res/values-sw720dp-land/dimens.xml b/libraries/AndroidBootstrap/res/values-sw720dp-land/dimens.xml new file mode 100644 index 000000000..61e3fa8fb --- /dev/null +++ b/libraries/AndroidBootstrap/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,9 @@ + + + + 128dp + + diff --git a/libraries/AndroidBootstrap/res/values-v11/styles.xml b/libraries/AndroidBootstrap/res/values-v11/styles.xml new file mode 100644 index 000000000..3c02242ad --- /dev/null +++ b/libraries/AndroidBootstrap/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/libraries/AndroidBootstrap/res/values-v14/styles.xml b/libraries/AndroidBootstrap/res/values-v14/styles.xml new file mode 100644 index 000000000..a91fd0372 --- /dev/null +++ b/libraries/AndroidBootstrap/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/libraries/AndroidBootstrap/res/values/attrs.xml b/libraries/AndroidBootstrap/res/values/attrs.xml new file mode 100644 index 000000000..2e56a622d --- /dev/null +++ b/libraries/AndroidBootstrap/res/values/attrs.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/AndroidBootstrap/res/values/colors.xml b/libraries/AndroidBootstrap/res/values/colors.xml new file mode 100644 index 000000000..f0615f3ef --- /dev/null +++ b/libraries/AndroidBootstrap/res/values/colors.xml @@ -0,0 +1,65 @@ + + + #ffffffff + #ff000000 + #ccc + #ffe0e0e0 + + #ff428bca + #ff357ebd + #ff3276b1 + #ff285e8e + #a5428bca + #a5357ebd + + #ffd9534f + #ffd43f3a + #ffd2322d + #ffac2925 + #a5d9534f + #a5d43f3a + + + #ff5cb85c + #ff4cae4c + #ff47a447 + #ff398439 + #a55cb85c + #a54cae4c + + + #fff0ad4e + #ffeea236 + #ffed9c28 + #ffd58512 + #a5f0ad4e + #a5eea236 + + #ff5bc0de + #ff46b8da + #ff39b3d7 + #ff269abc + #a55bc0de + #a546b8da + + #ffffffff + #ffcccccc + #ffebebeb + #ffadadad + #a5ffffff + #a5cccccc + + #ff0a0a0a + #ff141414 + #ff1f1f1f + #ff292929 + #a50a0a0a + #a5141414 + + #ffffffff + #e9e9e9 + #dbdbdb + #7e7e7e + + + diff --git a/libraries/AndroidBootstrap/res/values/dimens.xml b/libraries/AndroidBootstrap/res/values/dimens.xml new file mode 100644 index 000000000..ed16a8abe --- /dev/null +++ b/libraries/AndroidBootstrap/res/values/dimens.xml @@ -0,0 +1,14 @@ + + + + 16dp + 16dp + 5dp + + 3dp + + 4dp + 8dp + 12dp + 16dp + diff --git a/libraries/AndroidBootstrap/res/values/strings.xml b/libraries/AndroidBootstrap/res/values/strings.xml new file mode 100644 index 000000000..69aff74d5 --- /dev/null +++ b/libraries/AndroidBootstrap/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + BButton + Settings + Hello world! + + diff --git a/libraries/AndroidBootstrap/res/values/styles.xml b/libraries/AndroidBootstrap/res/values/styles.xml new file mode 100644 index 000000000..150753361 --- /dev/null +++ b/libraries/AndroidBootstrap/res/values/styles.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapButton.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapButton.java new file mode 100644 index 000000000..374d004a8 --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapButton.java @@ -0,0 +1,445 @@ +package com.beardedhen.androidbootstrap; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.R; + + +public class BootstrapButton extends FrameLayout { + + private static Map bbuttonTypeMap; + private static Map bbuttonTypeMapRounded; + private static Typeface font; + + private static Map faMap; + + private TextView lblMiddle; + private TextView lblRight; + private TextView lblLeft; + private ViewGroup layout; + private boolean roundedCorners = false; + private boolean fillparent = false; + + private static final String FA_ICON_QUESTION = "fa-question"; + + static{ + + bbuttonTypeMap = new HashMap(); + + bbuttonTypeMap.put("default", BootstrapTypes.DEFAULT); + bbuttonTypeMap.put("primary", BootstrapTypes.PRIMARY); + bbuttonTypeMap.put("success", BootstrapTypes.SUCCESS); + bbuttonTypeMap.put("info", BootstrapTypes.INFO); + bbuttonTypeMap.put("warning", BootstrapTypes.WARNING); + bbuttonTypeMap.put("danger", BootstrapTypes.DANGER); + bbuttonTypeMap.put("inverse", BootstrapTypes.INVERSE); + + bbuttonTypeMapRounded = new HashMap(); + + bbuttonTypeMapRounded.put("default", BootstrapTypes.DEFAULT_ROUNDED); + bbuttonTypeMapRounded.put("primary", BootstrapTypes.PRIMARY_ROUNDED); + bbuttonTypeMapRounded.put("success", BootstrapTypes.SUCCESS_ROUNDED); + bbuttonTypeMapRounded.put("info", BootstrapTypes.INFO_ROUNDED); + bbuttonTypeMapRounded.put("warning", BootstrapTypes.WARNING_ROUNDED); + bbuttonTypeMapRounded.put("danger", BootstrapTypes.DANGER_ROUNDED); + bbuttonTypeMapRounded.put("inverse", BootstrapTypes.INVERSE_ROUNDED); + + + faMap = FontAwesome.getFaMap(); + + } + + public BootstrapButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialise(attrs); + } + + public BootstrapButton(Context context, AttributeSet attrs) { + super(context, attrs); + initialise(attrs); + } + + public BootstrapButton(Context context) { + super(context); + initialise(null); + } + + //set up the bootstrap types + private enum BootstrapTypes + { + DEFAULT(R.drawable.bbuton_default, R.color.black), + PRIMARY(R.drawable.bbuton_primary, R.color.white), + SUCCESS(R.drawable.bbuton_success, R.color.white), + INFO(R.drawable.bbuton_info, R.color.white), + WARNING(R.drawable.bbuton_warning, R.color.white), + DANGER(R.drawable.bbuton_danger, R.color.white), + INVERSE(R.drawable.bbuton_inverse, R.color.white), + + DEFAULT_ROUNDED(R.drawable.bbuton_default_rounded, R.color.black), + PRIMARY_ROUNDED(R.drawable.bbuton_primary_rounded, R.color.white), + SUCCESS_ROUNDED(R.drawable.bbuton_success_rounded, R.color.white), + INFO_ROUNDED(R.drawable.bbuton_info_rounded, R.color.white), + WARNING_ROUNDED(R.drawable.bbuton_warning_rounded, R.color.white), + DANGER_ROUNDED(R.drawable.bbuton_danger_rounded, R.color.white), + INVERSE_ROUNDED(R.drawable.bbuton_inverse_rounded, R.color.white); + + private int backgroundDrawable; + private int textColour; + + BootstrapTypes(int backgroundDrawable, int textColour) + { + this.backgroundDrawable = backgroundDrawable; + this.textColour = textColour; + } + } + + + private void initialise( AttributeSet attrs ) + { + LayoutInflater inflator = (LayoutInflater)getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + //get font + readFont(getContext()); + + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.BootstrapButton); + + //defaults + BootstrapTypes type = null; + String bootstrapType = "default"; + String iconLeft = ""; + String iconRight = ""; + String text = ""; + //boolean roundedCorners = false; + float fontSize = 14.0f; + float scale = getResources().getDisplayMetrics().density; //for padding + String size = "default"; + int paddingA = (int) (10 *scale + 0.5f); + int paddingB = (int) (15 *scale + 0.5f); + + + //attribute values + + if (a.getString(R.styleable.BootstrapButton_bb_type) != null) { + bootstrapType = a.getString(R.styleable.BootstrapButton_bb_type); + } + + if (a.getString(R.styleable.BootstrapButton_bb_roundedCorners) != null) { + roundedCorners = a.getBoolean(R.styleable.BootstrapButton_bb_roundedCorners, false) ; + } + + if(a.getString(R.styleable.BootstrapButton_bb_size) != null) { + size = a.getString(R.styleable.BootstrapButton_bb_size); + } + + if ( a.getString(R.styleable.BootstrapButton_bb_icon_left) != null) { + iconLeft = a.getString(R.styleable.BootstrapButton_bb_icon_left ); + } + + if(a.getString(R.styleable.BootstrapButton_bb_icon_right) != null) { + iconRight = a.getString(R.styleable.BootstrapButton_bb_icon_right ); + } + + if(a.getString(R.styleable.BootstrapButton_android_text) != null) { + text = a.getString(R.styleable.BootstrapButton_android_text); + } + String gravity = ""; + if(a.getString(R.styleable.BootstrapButton_bb_text_gravity) != null) { + gravity = a.getString(R.styleable.BootstrapButton_bb_text_gravity); + } + + boolean enabled = true; + if(a.getString(R.styleable.BootstrapButton_android_enabled) != null) { + enabled = a.getBoolean(R.styleable.BootstrapButton_android_enabled, true); + } + + int layoutWidth = 0; + if(a.getString(R.styleable.BootstrapButton_android_layout_width) != null) { + layoutWidth = a.getInt(R.styleable.BootstrapButton_android_layout_width, 0); + } + + //works even if it's fill_parent or match_parent + if( (layoutWidth == LayoutParams.MATCH_PARENT)) { + fillparent = true; + } + + if(a.getString(R.styleable.BootstrapButton_android_textSize) != null) { + + //font sizes + String xmlProvidedSize = attrs.getAttributeValue( + "http://schemas.android.com/apk/res/android", "textSize"); + final Pattern PATTERN_FONT_SIZE = Pattern + .compile("([0-9]+[.]?[0-9]*)sp"); + Matcher m = PATTERN_FONT_SIZE.matcher(xmlProvidedSize); + + if (m.find()) { + + if (m.groupCount() == 1) { + + fontSize = Float.valueOf(m.group(1)); + } + + } + + } + + a.recycle(); + View v = null; + if(fillparent){ + v = inflator.inflate(R.layout.bootstrap_button_fill, null, false); + } else { + v = inflator.inflate(R.layout.bootstrap_button, null, false); + } + + + //set up font sizes and padding for different button sizes + if(size.equals("large")){ + fontSize = 20.0f; + paddingA = (int) (15 *scale + 0.5f);; + paddingB = (int) (20 *scale + 0.5f);; + } + + if(size.equals("small")){ + fontSize = 12.0f; + paddingA = (int) (5 *scale + 0.5f);; + paddingB = (int) (10 *scale + 0.5f);; + } + + if(size.equals("xsmall")){ + fontSize = 10.0f; + paddingA = (int) (2 *scale + 0.5f);; + paddingB = (int) (5 *scale + 0.5f);; + } + + //get layout items + layout = (ViewGroup) v.findViewById(R.id.layout); + lblLeft = (TextView) v.findViewById(R.id.lblLeft); + lblMiddle = (TextView) v.findViewById(R.id.lblMiddle); + lblRight = (TextView) v.findViewById(R.id.lblRight); + + //set the background + //setBootstrapType(bootstrapType); + + //get the correct background type + if(roundedCorners == true) + { + type = bbuttonTypeMapRounded.get(bootstrapType); + } else { + type = bbuttonTypeMap.get(bootstrapType); + } + + //set up as default + if (type == null) + { + type = BootstrapTypes.DEFAULT; + } + + //apply the background type + layout.setBackgroundResource(type.backgroundDrawable); + lblLeft.setTextColor(getResources().getColor(type.textColour)); + lblMiddle.setTextColor(getResources().getColor(type.textColour)); + lblRight.setTextColor(getResources().getColor(type.textColour)); + + //set the font awesome icon typeface + lblLeft.setTypeface(font); + lblRight.setTypeface(font); + + //set up the font size + lblLeft.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); + lblMiddle.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); + lblRight.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); + + //deal with gravity + + if(gravity.length() > 0) { + setTextGravity(gravity); + } + + + boolean onlyIcon = true; + + //set the text + if(text.length() > 0){ + lblMiddle.setText(text ); + lblMiddle.setVisibility(View.VISIBLE); + onlyIcon = false; + } + + //set up the padding + + if (iconLeft.length() > 0) { + //lblLeft.setText(iconLeft); + setLeftIcon(iconLeft); + lblLeft.setVisibility(View.VISIBLE); + + if (onlyIcon == false){ + lblLeft.setPadding(paddingB, 0, 0, 0); + } else { + lblLeft.setPadding(paddingB, 0, paddingB, 0); + } + + //padding for symmetry + if ( ( iconRight.length() == 0) && onlyIcon == false ) { + lblMiddle.setPadding(paddingA, 0, (int) paddingB, 0); + } + + } + + if (iconRight.length() > 0) { + //lblRight.setText(iconRight); + setRightIcon(iconRight); + lblRight.setVisibility(View.VISIBLE); + + if (onlyIcon == false){ + lblRight.setPadding(0, 0, paddingB, 0); + }else { + lblRight.setPadding(paddingB, 0, paddingB, 0); + } + + //padding for symmetry + if ( (iconLeft.length() == 0) && onlyIcon == false ) { + lblMiddle.setPadding(paddingB, 0, (int) paddingA, 0); + } + } + + if(iconLeft.length() > 0 && iconRight.length() > 0 ) + { + lblMiddle.setPadding(paddingA, 0, paddingA, 0); + } + this.setClickable(true); + + this.setEnabled(enabled); + + layout.setPadding(0, paddingB, 0, paddingB); + + addView(v); + } + + //static class to read in font + private static void readFont(Context context) + { + + if(font == null){ + try { + font = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + } catch (Exception e) { + Log.e("BootstrapButton", "Could not get typeface because " + e.getMessage()); + font = Typeface.DEFAULT; + } + } + + } + + + /** + * Changes the button text + * @param text - String value for what is displayed on the button + */ + public void setText(String text) { + lblMiddle.setText(text); + } + + + /** + * Changes the left icon on a BootstrapButton + * @param leftIcon- String value for the icon as per http://fortawesome.github.io/Font-Awesome/cheatsheet/ + */ + public void setLeftIcon(String leftIcon) { + + String icon = faMap.get(leftIcon); + + if (icon == null) + { + icon = faMap.get(FA_ICON_QUESTION); + } + + lblLeft.setText(icon); + } + + /** + * Changes the right icon on a BootstrapButton + * @param rightIcon - String value for the icon as per http://fortawesome.github.io/Font-Awesome/cheatsheet/ + */ + public void setRightIcon(String rightIcon) { + + String icon = faMap.get(rightIcon); + + if (icon == null) + { + icon = faMap.get(FA_ICON_QUESTION); + } + + lblRight.setText(icon); + + } + + /** + * Changes the type of BootstrapButton + * @param bootstrapType - String value for the type of button e.g. "primary" + */ + public void setBootstrapType(String bootstrapType) { + + BootstrapTypes type = null; + + //get the correct background type + if (roundedCorners == true) { + type = bbuttonTypeMapRounded.get(bootstrapType); + } else { + type = bbuttonTypeMap.get(bootstrapType); + } + + //set up as default + if (type == null) { + type = BootstrapTypes.DEFAULT; + } + + + layout.setBackgroundResource(type.backgroundDrawable); + lblLeft.setTextColor(getResources().getColor(type.textColour)); + lblMiddle.setTextColor(getResources().getColor(type.textColour)); + lblRight.setTextColor(getResources().getColor(type.textColour)); + + } + + /** + * Specifies whether the BootstrapButton is enabled or disabled + * @param enabled - boolean state for either enabled or disabled + */ + public void setBootstrapButtonEnabled(boolean enabled) + { + this.setEnabled(enabled); + } + + + /** + * Changes the gravity for the text on a bootstrap button that is not wrap_content + * @param gravity - string for either center, right, or left. + */ + public void setTextGravity(String gravity) { + if(gravity.equals("left")) { + lblMiddle.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + } else if (gravity.equals("center")) { + lblMiddle.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); + } else if (gravity.equals("right")) { + lblMiddle.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL); + } + + } +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapCircleThumbnail.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapCircleThumbnail.java new file mode 100644 index 000000000..1eb353770 --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapCircleThumbnail.java @@ -0,0 +1,215 @@ +package com.beardedhen.androidbootstrap; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.utils.ImageUtils; + +public class BootstrapCircleThumbnail extends FrameLayout +{ + private static final int PADDING_SMALL = 4; + private static final int PADDING_MEDIUM = 4; + private static final int PADDING_LARGE = 6; + private static final int PADDING_XLARGE = 8; + + private static final int SIZE_SMALL = 48; //dp total size (outer circle) + private static final int SIZE_MEDIUM = 80;//dp + private static final int SIZE_LARGE = 112;//dp + private static final int SIZE_XLARGE = 176;//dp + private static final int SIZE_DEFAULT = SIZE_MEDIUM; + + private static final String SMALL = "small"; + private static final String MEDIUM = "medium"; + private static final String LARGE = "large"; + private static final String XLARGE = "xlarge"; + + private LinearLayout container; + private LinearLayout placeholder; + private ImageView image; + private TextView dimensionsLabel; + private String size = MEDIUM; + private boolean minimal = false;//minimal means display just the image, no padding + private String text = ""; + private int imageWidth = SIZE_DEFAULT; + private int imageHeight = SIZE_DEFAULT; + private int padding = 0; + + public BootstrapCircleThumbnail(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initialise(attrs); + } + + public BootstrapCircleThumbnail(Context context, AttributeSet attrs) + { + super(context, attrs); + initialise(attrs); + } + + public BootstrapCircleThumbnail(Context context) + { + super(context); + initialise(null); + } + + private void initialise( AttributeSet attrs ) + { + LayoutInflater inflator = (LayoutInflater)getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.BootstrapCircleThumbnail); + + + int imageDrawable = 0; + + if(a.getString(R.styleable.BootstrapCircleThumbnail_bct_image) != null) + { + imageDrawable = a.getResourceId(R.styleable.BootstrapCircleThumbnail_bct_image, 0); + + } + + if(a.getString(R.styleable.BootstrapCircleThumbnail_android_text) != null) + { + text = a.getString(R.styleable.BootstrapCircleThumbnail_android_text); + } + + if(a.getString(R.styleable.BootstrapCircleThumbnail_bct_size) != null) + { + this.size = a.getString(R.styleable.BootstrapCircleThumbnail_bct_size); + } + + if(a.getString(R.styleable.BootstrapCircleThumbnail_bct_minimal) != null) + { + this.minimal = a.getBoolean(R.styleable.BootstrapCircleThumbnail_bct_minimal, false); + } + + a.recycle(); + + View v = inflator.inflate(R.layout.bootstrap_thumbnail_circle, null, false); + dimensionsLabel = (TextView) v.findViewById(R.id.dimensionsLabel); + container = (LinearLayout) v.findViewById(R.id.container); + placeholder = (LinearLayout) v.findViewById(R.id.placeholder); + image = (ImageView) v.findViewById(R.id.image); + float scale = getResources().getDisplayMetrics().density; + + + + //small image + if(this.size.equals(SMALL)) + { + padding = PADDING_SMALL; + imageWidth = SIZE_SMALL; + imageHeight = SIZE_SMALL; + + } + else if(this.size.equals(MEDIUM)) + { + padding = PADDING_MEDIUM; + imageWidth = SIZE_MEDIUM; + imageHeight = SIZE_MEDIUM; + } + else if(this.size.equals(LARGE)) + { + padding = PADDING_LARGE; + imageWidth = SIZE_LARGE; + imageHeight = SIZE_LARGE; + } + else if(this.size.equals(XLARGE)) + { + padding = PADDING_XLARGE; + imageWidth = SIZE_XLARGE; + imageHeight = SIZE_XLARGE; + } + //no valid size is given, set image to default size + else + { + padding = PADDING_MEDIUM; + imageWidth = SIZE_DEFAULT; + imageHeight = SIZE_DEFAULT; + } + + //convert padding to pixels + DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); + int paddingPX = (int)((padding * scale) + 0.5); + + //convert image size to pixels + int imageSizeWidthPX = (int)((imageWidth * scale) + 0.5); + int imageSizeHeightPX = (int)((imageHeight * scale) + 0.5); + + //make inner image smaller to compensate for the padding so that entire circle including padding equals the size + //ex. small image = 48dp, small padding = 4dp, inner image = 48 - (4 * 2) = 40 + if(this.minimal == false) + { + imageSizeWidthPX = imageSizeWidthPX - (paddingPX * 2); + imageSizeHeightPX = imageSizeHeightPX - (paddingPX * 2); + + this.container.setPadding(paddingPX, paddingPX, paddingPX, paddingPX); + container.setBackgroundResource(R.drawable.thumbnail_circle_container); + } + else + { + container.setBackgroundResource(R.drawable.thumbnail_circle_minimal); + } + + //if no image is given + if(imageDrawable == 0) + { + this.image.setVisibility(View.GONE); + placeholder.setLayoutParams(new LinearLayout.LayoutParams(imageSizeWidthPX, imageSizeHeightPX)); + placeholder.setPadding(paddingPX, paddingPX, paddingPX, paddingPX); + + //set placeholder image + placeholder.setBackgroundResource(R.drawable.thumbnail_circle); + + this.dimensionsLabel.setText(text); + } + else + { + placeholder.setPadding(0, 0, 0, 0); + this.dimensionsLabel.setVisibility(View.GONE); + Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), imageDrawable); + + Bitmap roundBitmap = ImageUtils.getCircleBitmap(bitmap, imageSizeWidthPX, imageSizeHeightPX); + image.setImageBitmap(roundBitmap); + } + + this.addView(v); + } + + public void setImage(int drawable) + { + Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), drawable); + + float scale = getResources().getDisplayMetrics().density; + + //convert image size to pixels + int widthPX = (int)((this.imageWidth * scale) + 0.5); + int heightPX = (int)((this.imageHeight * scale) + 0.5); + + int paddingPX = (int)((this.padding * scale) + 0.5); + + if(this.minimal == false) + { + widthPX = widthPX - (paddingPX * 2); + heightPX = heightPX - (paddingPX * 2); + } + + Bitmap roundBitmap = ImageUtils.getCircleBitmap(bitmap, widthPX, heightPX); + image.setImageBitmap(roundBitmap); + + invalidate(); + requestLayout(); + } +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapEditText.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapEditText.java new file mode 100644 index 000000000..c258f8a09 --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapEditText.java @@ -0,0 +1,188 @@ +package com.beardedhen.androidbootstrap; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.EditText; + +public class BootstrapEditText extends EditText { + + private boolean roundedCorners = false; + + public BootstrapEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialise(attrs); + } + + public BootstrapEditText(Context context, AttributeSet attrs) { + super(context, attrs); + initialise(attrs); + } + + public BootstrapEditText(Context context) { + super(context); + initialise(null); + } + + public static final String BOOTSTRAP_EDIT_TEXT_DEFAULT = "default"; + public static final String BOOTSTRAP_EDIT_TEXT_SUCCESS = "success"; + public static final String BOOTSTRAP_EDIT_TEXT_WARNING = "warning"; + public static final String BOOTSTRAP_EDIT_TEXT_DANGER = "danger"; + + + private void initialise( AttributeSet attrs ) + { + + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BootstrapEditText); + + //get defaults + float fontSize = 14.0f; + String state = "default"; + String text = ""; + String hint = ""; + boolean enabled = true; + + //font size + if (a.getString(R.styleable.BootstrapEditText_android_textSize) != null) { + + String xmlProvidedSize = attrs.getAttributeValue( "http://schemas.android.com/apk/res/android", "textSize"); + final Pattern PATTERN_FONT_SIZE = Pattern + .compile("([0-9]+[.]?[0-9]*)sp"); + Matcher m = PATTERN_FONT_SIZE.matcher(xmlProvidedSize); + + if (m.find()) { + if (m.groupCount() == 1) { + fontSize = Float.valueOf(m.group(1)); + } + } + } + + //rounded corners + if(a.getString(R.styleable.BootstrapEditText_be_roundedCorners) != null) { + roundedCorners = a.getBoolean(R.styleable.BootstrapEditText_be_roundedCorners, false); + } + + //state + if(a.getString(R.styleable.BootstrapEditText_be_state) != null) { + state = a.getString(R.styleable.BootstrapEditText_be_state); + } + + //text + if(a.getString(R.styleable.BootstrapEditText_android_text) != null) { + text = a.getString(R.styleable.BootstrapEditText_android_text); + } + + //hint + if(a.getString(R.styleable.BootstrapEditText_android_hint) != null) { + hint = a.getString(R.styleable.BootstrapEditText_android_hint); + } + + //enabled + if(a.getString(R.styleable.BootstrapEditText_android_enabled) != null) { + enabled = a.getBoolean(R.styleable.BootstrapEditText_android_enabled, true); + } + + //set values + this.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); + this.setText(text); + this.setHint(hint); + this.setEnabled(enabled); + + if (enabled){ + //work out the right background + setBackgroundDrawable(state); + + } + + a.recycle(); + + //addView(editTextView); + } + + + private void setBackgroundDrawable(String state) + { + if(roundedCorners){ + this.setBackgroundResource(R.drawable.edittext_background_rounded); + } else { + this.setBackgroundResource(R.drawable.edittext_background); + } + + if(roundedCorners){ + + if (state.equals(BOOTSTRAP_EDIT_TEXT_SUCCESS)){ + this.setBackgroundResource(R.drawable.edittext_background_rounded_success); + } else if (state.equals(BOOTSTRAP_EDIT_TEXT_WARNING)){ + this.setBackgroundResource(R.drawable.edittext_background_rounded_warning); + } else if (state.equals(BOOTSTRAP_EDIT_TEXT_DANGER)){ + this.setBackgroundResource(R.drawable.edittext_background_rounded_danger); + } + + } else { + + if (state.equals(BOOTSTRAP_EDIT_TEXT_SUCCESS)){ + this.setBackgroundResource(R.drawable.edittext_background_success); + } else if (state.equals(BOOTSTRAP_EDIT_TEXT_WARNING)){ + this.setBackgroundResource(R.drawable.edittext_background_warning); + } else if (state.equals(BOOTSTRAP_EDIT_TEXT_DANGER)){ + this.setBackgroundResource(R.drawable.edittext_background_danger); + } + + } + } + + + /** + * Change the BootstrapEditTextState + * @param state + */ + public void setState(String state){ + setBackgroundDrawable(state); + } + + /** + * Set the BootstrapEditText to a successful state + */ + public void setSuccess() + { + setBackgroundDrawable(BOOTSTRAP_EDIT_TEXT_SUCCESS); + } + + /** + * Set the BootstrapEditText to a warning state + */ + public void setWarning() + { + setBackgroundDrawable(BOOTSTRAP_EDIT_TEXT_WARNING); + } + + /** + * Set the BootstrapEditText to a danger state + */ + public void setDanger() + { + setBackgroundDrawable(BOOTSTRAP_EDIT_TEXT_DANGER); + } + + /** + * Set the BootstrapEditText to a default state + */ + public void setDefault() + { + setBackgroundDrawable(BOOTSTRAP_EDIT_TEXT_DEFAULT); + } + + /** + * Specifies whether the BootstrapEditText is enabled or disabled + * @param enabled - boolean state for either enabled or disabled + */ + public void setBootstrapEditTextEnabled(boolean enabled) + { + this.setEnabled(enabled); + } + +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapThumbnail.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapThumbnail.java new file mode 100644 index 000000000..a4e1ba1bc --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/BootstrapThumbnail.java @@ -0,0 +1,209 @@ +package com.beardedhen.androidbootstrap; + +import java.util.HashMap; +import java.util.Map; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class BootstrapThumbnail extends FrameLayout +{ + private static final int DEFAULT_WIDTH = 150; //width of thumbnail when no width is given + private static final int DEFAULT_HEIGHT = 150;//height of thumbnail when no height is given + private static final int DEFAULT_MAX_PADDING = 8; //8dp is max padding size when padding isn't specified by user + private static final int DEFAULT_MIN_PADDING = 4; //4dp + private static final String DEFAULT_TYPE = "rounded"; + + private static Map bThumbnailTypeMap; + private static Typeface font; + private ViewGroup container; + private LinearLayout placeholder; + private TextView dimensionsLabel; + private boolean roundedCorners = true; + + static{ + bThumbnailTypeMap = new HashMap(); + + bThumbnailTypeMap.put("rounded", ThumbnailTypes.ROUNDED);//default is rounded if user doesn't specify to use square + bThumbnailTypeMap.put("square", ThumbnailTypes.SQUARE); + } + + public BootstrapThumbnail(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + initialise(attrs); + } + + public BootstrapThumbnail(Context context, AttributeSet attrs) + { + super(context, attrs); + initialise(attrs); + } + + public BootstrapThumbnail(Context context) + { + super(context); + initialise(null); + } + + public void setImage(int drawable) + { + this.placeholder.setBackgroundResource(drawable); + invalidate(); + requestLayout(); + } + + //set up the bootstrap types + private enum ThumbnailTypes + { + ROUNDED(R.drawable.bthumbnail_container_rounded, R.drawable.bthumbnail_placeholder_default), + SQUARE(R.drawable.bthumbnail_container_square, R.drawable.bthumbnail_placeholder_default); + + private int containerDrawable; + private int placeholderDrawable; + + ThumbnailTypes(int containerDrawable, int placeholderDrawable) + { + this.containerDrawable = containerDrawable; + this.placeholderDrawable = placeholderDrawable; + } + } + + private void initialise( AttributeSet attrs ) + { + LayoutInflater inflator = (LayoutInflater)getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + readFont(getContext()); + + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.BootstrapThumbnail); + + //defaults + ThumbnailTypes type = null; + String thumbnailType = DEFAULT_TYPE; + String text = ""; + int imageDrawable = 0; + float scale = getResources().getDisplayMetrics().density; //for padding + int width = DEFAULT_WIDTH; + int height = DEFAULT_HEIGHT; + int padding = 0; + int paddingDP = 0; + + //attribute values + if(a.getString(R.styleable.BootstrapThumbnail_bt_width) != null) { + width = (int) a.getDimension(R.styleable.BootstrapThumbnail_bt_width, 0); + Log.v("width", Integer.toString(width)); + } + + if(a.getString(R.styleable.BootstrapThumbnail_bt_height) != null) { + height = (int) a.getDimension(R.styleable.BootstrapThumbnail_bt_height, 0); + } + + if(a.getString(R.styleable.BootstrapThumbnail_bt_inside_padding) != null) { + paddingDP = (int) a.getDimension(R.styleable.BootstrapThumbnail_bt_inside_padding, 0); + } + else{ + padding = (int) (((Math.sqrt(width * height)) / 100) * 2); + if(padding > DEFAULT_MAX_PADDING) + padding = DEFAULT_MAX_PADDING; + if(padding < DEFAULT_MIN_PADDING) + padding = DEFAULT_MIN_PADDING; + + paddingDP = (int) (padding * scale + 0.5f);//container padding in DP + } + + if(a.getString(R.styleable.BootstrapThumbnail_bt_roundedCorners) != null){ + roundedCorners = a.getBoolean(R.styleable.BootstrapThumbnail_bt_roundedCorners, false) ; + } + + if(a.getString(R.styleable.BootstrapThumbnail_bt_image) != null){ + imageDrawable = a.getResourceId(R.styleable.BootstrapThumbnail_bt_image, 0); + } + + a.recycle(); + + text = (int)(width/scale) + "x" + (int)(height/scale); + View v = inflator.inflate(R.layout.bootstrap_thumbnail, null, false); + + //get layout items + container = (ViewGroup) v.findViewById(R.id.container); + placeholder = (LinearLayout) v.findViewById(R.id.placeholder); + dimensionsLabel = (TextView) v.findViewById(R.id.dimensionsLabel); + + Log.v("size", "width:" + width + " height:" + height); + + + type = bThumbnailTypeMap.get(thumbnailType); + + //get the correct background type + if(roundedCorners == true) + { + type = bThumbnailTypeMap.get("rounded"); + } else { + type = bThumbnailTypeMap.get("square"); + } + + //apply the background type + container.setBackgroundResource(type.containerDrawable); + + //if no image is provided by user + if(imageDrawable == 0){ + //set default grey placeholder background + placeholder.setBackgroundResource(type.placeholderDrawable); + + //set the text + if(text.length() > 0){ + dimensionsLabel.setText(text); + dimensionsLabel.setVisibility(View.VISIBLE); + } + } + else{ + //set background to user's provided image + placeholder.setBackgroundResource(imageDrawable); + + //remove textview dimensions + dimensionsLabel.setVisibility(View.GONE); + } + + //placeholder padding + int paddingP = (int) (((Math.sqrt(width * height)) / 100) * 4); + + //convert to DP + int paddingDPP = (int) (paddingP * scale + 0.5f);//placeholder padding in DP + + container.setPadding(paddingDP, paddingDP, paddingDP, paddingDP); + placeholder.setPadding(paddingDPP, paddingDPP, paddingDPP, paddingDPP); + + placeholder.setLayoutParams(new LinearLayout.LayoutParams(width,height)); + + //set the font awesome icon typeface + dimensionsLabel.setTypeface(font); + + this.setClickable(true); + + addView(v); + } + + //static class to read in font + private static void readFont(Context context) + { + if(font == null){ + try { + font = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + } catch (Exception e) { + Log.e("BootstrapButton", "Could not get typeface because " + e.getMessage()); + font = Typeface.DEFAULT; + } + } + + } +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesome.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesome.java new file mode 100644 index 000000000..2038094ac --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesome.java @@ -0,0 +1,390 @@ +package com.beardedhen.androidbootstrap; + +import java.util.HashMap; +import java.util.Map; + +public class FontAwesome { + + private static Map faMap = new HashMap(); + + //font awesome map as per + //http://fortawesome.github.io/Font-Awesome/cheatsheet/ + + static { + faMap.put("fa-glass", "\uf000"); + faMap.put("fa-music", "\uf001"); + faMap.put("fa-search", "\uf002"); + faMap.put("fa-envelope-o", "\uf003"); + faMap.put("fa-heart", "\uf004"); + faMap.put("fa-star", "\uf005"); + faMap.put("fa-star-o", "\uf006"); + faMap.put("fa-user", "\uf007"); + faMap.put("fa-film", "\uf008"); + faMap.put("fa-th-large", "\uf009"); + faMap.put("fa-th", "\uf00a"); + faMap.put("fa-th-list", "\uf00b"); + faMap.put("fa-check", "\uf00c"); + faMap.put("fa-times", "\uf00d"); + faMap.put("fa-search-plus", "\uf00e"); + faMap.put("fa-search-minus", "\uf010"); + faMap.put("fa-power-off", "\uf011"); + faMap.put("fa-signal", "\uf012"); + faMap.put("fa-cog", "\uf013"); + faMap.put("fa-trash-o", "\uf014"); + faMap.put("fa-home", "\uf015"); + faMap.put("fa-file-o", "\uf016"); + faMap.put("fa-clock-o", "\uf017"); + faMap.put("fa-road", "\uf018"); + faMap.put("fa-download", "\uf019"); + faMap.put("fa-arrow-circle-o-down", "\uf01a"); + faMap.put("fa-arrow-circle-o-up", "\uf01b"); + faMap.put("fa-inbox", "\uf01c"); + faMap.put("fa-play-circle-o", "\uf01d"); + faMap.put("fa-repeat", "\uf01e"); + faMap.put("fa-refresh", "\uf021"); + faMap.put("fa-list-alt", "\uf022"); + faMap.put("fa-lock", "\uf023"); + faMap.put("fa-flag", "\uf024"); + faMap.put("fa-headphones", "\uf025"); + faMap.put("fa-volume-off", "\uf026"); + faMap.put("fa-volume-down", "\uf027"); + faMap.put("fa-volume-up", "\uf028"); + faMap.put("fa-qrcode", "\uf029"); + faMap.put("fa-barcode", "\uf02a"); + faMap.put("fa-tag", "\uf02b"); + faMap.put("fa-tags", "\uf02c"); + faMap.put("fa-book", "\uf02d"); + faMap.put("fa-bookmark", "\uf02e"); + faMap.put("fa-print", "\uf02f"); + faMap.put("fa-camera", "\uf030"); + faMap.put("fa-font", "\uf031"); + faMap.put("fa-bold", "\uf032"); + faMap.put("fa-italic", "\uf033"); + faMap.put("fa-text-height", "\uf034"); + faMap.put("fa-text-width", "\uf035"); + faMap.put("fa-align-left", "\uf036"); + faMap.put("fa-align-center", "\uf037"); + faMap.put("fa-align-right", "\uf038"); + faMap.put("fa-align-justify", "\uf039"); + faMap.put("fa-list", "\uf03a"); + faMap.put("fa-outdent", "\uf03b"); + faMap.put("fa-indent", "\uf03c"); + faMap.put("fa-video-camera", "\uf03d"); + faMap.put("fa-picture-o", "\uf03e"); + faMap.put("fa-pencil", "\uf040"); + faMap.put("fa-map-marker", "\uf041"); + faMap.put("fa-adjust", "\uf042"); + faMap.put("fa-tint", "\uf043"); + faMap.put("fa-pencil-square-o", "\uf044"); + faMap.put("fa-share-square-o", "\uf045"); + faMap.put("fa-check-square-o", "\uf046"); + faMap.put("fa-move", "\uf047"); + faMap.put("fa-step-backward", "\uf048"); + faMap.put("fa-fast-backward", "\uf049"); + faMap.put("fa-backward", "\uf04a"); + faMap.put("fa-play", "\uf04b"); + faMap.put("fa-pause", "\uf04c"); + faMap.put("fa-stop", "\uf04d"); + faMap.put("fa-forward", "\uf04e"); + faMap.put("fa-fast-forward", "\uf050"); + faMap.put("fa-step-forward", "\uf051"); + faMap.put("fa-eject", "\uf052"); + faMap.put("fa-chevron-left", "\uf053"); + faMap.put("fa-chevron-right", "\uf054"); + faMap.put("fa-plus-circle", "\uf055"); + faMap.put("fa-minus-circle", "\uf056"); + faMap.put("fa-times-circle", "\uf057"); + faMap.put("fa-check-circle", "\uf058"); + faMap.put("fa-question-circle", "\uf059"); + faMap.put("fa-info-circle", "\uf05a"); + faMap.put("fa-crosshairs", "\uf05b"); + faMap.put("fa-times-circle-o", "\uf05c"); + faMap.put("fa-check-circle-o", "\uf05d"); + faMap.put("fa-ban", "\uf05e"); + faMap.put("fa-arrow-left", "\uf060"); + faMap.put("fa-arrow-right", "\uf061"); + faMap.put("fa-arrow-up", "\uf062"); + faMap.put("fa-arrow-down", "\uf063"); + faMap.put("fa-share", "\uf064"); + faMap.put("fa-resize-full", "\uf065"); + faMap.put("fa-resize-small", "\uf066"); + faMap.put("fa-plus", "\uf067"); + faMap.put("fa-minus", "\uf068"); + faMap.put("fa-asterisk", "\uf069"); + faMap.put("fa-exclamation-circle", "\uf06a"); + faMap.put("fa-gift", "\uf06b"); + faMap.put("fa-leaf", "\uf06c"); + faMap.put("fa-fire", "\uf06d"); + faMap.put("fa-eye", "\uf06e"); + faMap.put("fa-eye-slash", "\uf070"); + faMap.put("fa-exclamation-triangle", "\uf071"); + faMap.put("fa-plane", "\uf072"); + faMap.put("fa-calendar", "\uf073"); + faMap.put("fa-random", "\uf074"); + faMap.put("fa-comment", "\uf075"); + faMap.put("fa-magnet", "\uf076"); + faMap.put("fa-chevron-up", "\uf077"); + faMap.put("fa-chevron-down", "\uf078"); + faMap.put("fa-retweet", "\uf079"); + faMap.put("fa-shopping-cart", "\uf07a"); + faMap.put("fa-folder", "\uf07b"); + faMap.put("fa-folder-open", "\uf07c"); + faMap.put("fa-resize-vertical", "\uf07d"); + faMap.put("fa-resize-horizontal", "\uf07e"); + faMap.put("fa-bar-chart-o", "\uf080"); + faMap.put("fa-twitter-square", "\uf081"); + faMap.put("fa-facebook-square", "\uf082"); + faMap.put("fa-camera-retro", "\uf083"); + faMap.put("fa-key", "\uf084"); + faMap.put("fa-cogs", "\uf085"); + faMap.put("fa-comments", "\uf086"); + faMap.put("fa-thumbs-o-up", "\uf087"); + faMap.put("fa-thumbs-o-down", "\uf088"); + faMap.put("fa-star-half", "\uf089"); + faMap.put("fa-heart-o", "\uf08a"); + faMap.put("fa-sign-out", "\uf08b"); + faMap.put("fa-linkedin-square", "\uf08c"); + faMap.put("fa-thumb-tack", "\uf08d"); + faMap.put("fa-external-link", "\uf08e"); + faMap.put("fa-sign-in", "\uf090"); + faMap.put("fa-trophy", "\uf091"); + faMap.put("fa-github-square", "\uf092"); + faMap.put("fa-upload", "\uf093"); + faMap.put("fa-lemon-o", "\uf094"); + faMap.put("fa-phone", "\uf095"); + faMap.put("fa-square-o", "\uf096"); + faMap.put("fa-bookmark-o", "\uf097"); + faMap.put("fa-phone-square", "\uf098"); + faMap.put("fa-twitter", "\uf099"); + faMap.put("fa-facebook", "\uf09a"); + faMap.put("fa-github", "\uf09b"); + faMap.put("fa-unlock", "\uf09c"); + faMap.put("fa-credit-card", "\uf09d"); + faMap.put("fa-rss", "\uf09e"); + faMap.put("fa-hdd", "\uf0a0"); + faMap.put("fa-bullhorn", "\uf0a1"); + faMap.put("fa-bell", "\uf0f3"); + faMap.put("fa-certificate", "\uf0a3"); + faMap.put("fa-hand-o-right", "\uf0a4"); + faMap.put("fa-hand-o-left", "\uf0a5"); + faMap.put("fa-hand-o-up", "\uf0a6"); + faMap.put("fa-hand-o-down", "\uf0a7"); + faMap.put("fa-arrow-circle-left", "\uf0a8"); + faMap.put("fa-arrow-circle-right", "\uf0a9"); + faMap.put("fa-arrow-circle-up", "\uf0aa"); + faMap.put("fa-arrow-circle-down", "\uf0ab"); + faMap.put("fa-globe", "\uf0ac"); + faMap.put("fa-wrench", "\uf0ad"); + faMap.put("fa-tasks", "\uf0ae"); + faMap.put("fa-filter", "\uf0b0"); + faMap.put("fa-briefcase", "\uf0b1"); + faMap.put("fa-fullscreen", "\uf0b2"); + faMap.put("fa-group", "\uf0c0"); + faMap.put("fa-link", "\uf0c1"); + faMap.put("fa-cloud", "\uf0c2"); + faMap.put("fa-flask", "\uf0c3"); + faMap.put("fa-scissors", "\uf0c4"); + faMap.put("fa-files-o", "\uf0c5"); + faMap.put("fa-paperclip", "\uf0c6"); + faMap.put("fa-floppy-o", "\uf0c7"); + faMap.put("fa-square", "\uf0c8"); + faMap.put("fa-reorder", "\uf0c9"); + faMap.put("fa-list-ul", "\uf0ca"); + faMap.put("fa-list-ol", "\uf0cb"); + faMap.put("fa-strikethrough", "\uf0cc"); + faMap.put("fa-underline", "\uf0cd"); + faMap.put("fa-table", "\uf0ce"); + faMap.put("fa-magic", "\uf0d0"); + faMap.put("fa-truck", "\uf0d1"); + faMap.put("fa-pinterest", "\uf0d2"); + faMap.put("fa-pinterest-square", "\uf0d3"); + faMap.put("fa-google-plus-square", "\uf0d4"); + faMap.put("fa-google-plus", "\uf0d5"); + faMap.put("fa-money", "\uf0d6"); + faMap.put("fa-caret-down", "\uf0d7"); + faMap.put("fa-caret-up", "\uf0d8"); + faMap.put("fa-caret-left", "\uf0d9"); + faMap.put("fa-caret-right", "\uf0da"); + faMap.put("fa-columns", "\uf0db"); + faMap.put("fa-sort", "\uf0dc"); + faMap.put("fa-sort-asc", "\uf0dd"); + faMap.put("fa-sort-desc", "\uf0de"); + faMap.put("fa-envelope", "\uf0e0"); + faMap.put("fa-linkedin", "\uf0e1"); + faMap.put("fa-undo", "\uf0e2"); + faMap.put("fa-gavel", "\uf0e3"); + faMap.put("fa-tachometer", "\uf0e4"); + faMap.put("fa-comment-o", "\uf0e5"); + faMap.put("fa-comments-o", "\uf0e6"); + faMap.put("fa-bolt", "\uf0e7"); + faMap.put("fa-sitemap", "\uf0e8"); + faMap.put("fa-umbrella", "\uf0e9"); + faMap.put("fa-clipboard", "\uf0ea"); + faMap.put("fa-lightbulb-o", "\uf0eb"); + faMap.put("fa-exchange", "\uf0ec"); + faMap.put("fa-cloud-download", "\uf0ed"); + faMap.put("fa-cloud-upload", "\uf0ee"); + faMap.put("fa-user-md", "\uf0f0"); + faMap.put("fa-stethoscope", "\uf0f1"); + faMap.put("fa-suitcase", "\uf0f2"); + faMap.put("fa-bell-o", "\uf0a2"); + faMap.put("fa-coffee", "\uf0f4"); + faMap.put("fa-cutlery", "\uf0f5"); + faMap.put("fa-file-text-o", "\uf0f6"); + faMap.put("fa-building", "\uf0f7"); + faMap.put("fa-hospital", "\uf0f8"); + faMap.put("fa-ambulance", "\uf0f9"); + faMap.put("fa-medkit", "\uf0fa"); + faMap.put("fa-fighter-jet", "\uf0fb"); + faMap.put("fa-beer", "\uf0fc"); + faMap.put("fa-h-square", "\uf0fd"); + faMap.put("fa-plus-square", "\uf0fe"); + faMap.put("fa-angle-double-left", "\uf100"); + faMap.put("fa-angle-double-right", "\uf101"); + faMap.put("fa-angle-double-up", "\uf102"); + faMap.put("fa-angle-double-down", "\uf103"); + faMap.put("fa-angle-left", "\uf104"); + faMap.put("fa-angle-right", "\uf105"); + faMap.put("fa-angle-up", "\uf106"); + faMap.put("fa-angle-down", "\uf107"); + faMap.put("fa-desktop", "\uf108"); + faMap.put("fa-laptop", "\uf109"); + faMap.put("fa-tablet", "\uf10a"); + faMap.put("fa-mobile", "\uf10b"); + faMap.put("fa-circle-o", "\uf10c"); + faMap.put("fa-quote-left", "\uf10d"); + faMap.put("fa-quote-right", "\uf10e"); + faMap.put("fa-spinner", "\uf110"); + faMap.put("fa-circle", "\uf111"); + faMap.put("fa-reply", "\uf112"); + faMap.put("fa-github-alt", "\uf113"); + faMap.put("fa-folder-o", "\uf114"); + faMap.put("fa-folder-open-o", "\uf115"); + faMap.put("fa-expand-o", "\uf116"); + faMap.put("fa-collapse-o", "\uf117"); + faMap.put("fa-smile-o", "\uf118"); + faMap.put("fa-frown-o", "\uf119"); + faMap.put("fa-meh-o", "\uf11a"); + faMap.put("fa-gamepad", "\uf11b"); + faMap.put("fa-keyboard-o", "\uf11c"); + faMap.put("fa-flag-o", "\uf11d"); + faMap.put("fa-flag-checkered", "\uf11e"); + faMap.put("fa-terminal", "\uf120"); + faMap.put("fa-code", "\uf121"); + faMap.put("fa-reply-all", "\uf122"); + faMap.put("fa-mail-reply-all", "\uf122"); + faMap.put("fa-star-half-o", "\uf123"); + faMap.put("fa-location-arrow", "\uf124"); + faMap.put("fa-crop", "\uf125"); + faMap.put("fa-code-fork", "\uf126"); + faMap.put("fa-chain-broken", "\uf127"); + faMap.put("fa-question", "\uf128"); + faMap.put("fa-info", "\uf129"); + faMap.put("fa-exclamation", "\uf12a"); + faMap.put("fa-superscript", "\uf12b"); + faMap.put("fa-subscript", "\uf12c"); + faMap.put("fa-eraser", "\uf12d"); + faMap.put("fa-puzzle-piece", "\uf12e"); + faMap.put("fa-microphone", "\uf130"); + faMap.put("fa-microphone-slash", "\uf131"); + faMap.put("fa-shield", "\uf132"); + faMap.put("fa-calendar-o", "\uf133"); + faMap.put("fa-fire-extinguisher", "\uf134"); + faMap.put("fa-rocket", "\uf135"); + faMap.put("fa-maxcdn", "\uf136"); + faMap.put("fa-chevron-circle-left", "\uf137"); + faMap.put("fa-chevron-circle-right", "\uf138"); + faMap.put("fa-chevron-circle-up", "\uf139"); + faMap.put("fa-chevron-circle-down", "\uf13a"); + faMap.put("fa-html5", "\uf13b"); + faMap.put("fa-css3", "\uf13c"); + faMap.put("fa-anchor", "\uf13d"); + faMap.put("fa-unlock-o", "\uf13e"); + faMap.put("fa-bullseye", "\uf140"); + faMap.put("fa-ellipsis-horizontal", "\uf141"); + faMap.put("fa-ellipsis-vertical", "\uf142"); + faMap.put("fa-rss-square", "\uf143"); + faMap.put("fa-play-circle", "\uf144"); + faMap.put("fa-ticket", "\uf145"); + faMap.put("fa-minus-square", "\uf146"); + faMap.put("fa-minus-square-o", "\uf147"); + faMap.put("fa-level-up", "\uf148"); + faMap.put("fa-level-down", "\uf149"); + faMap.put("fa-check-square", "\uf14a"); + faMap.put("fa-pencil-square", "\uf14b"); + faMap.put("fa-external-link-square", "\uf14c"); + faMap.put("fa-share-square", "\uf14d"); + faMap.put("fa-compass", "\uf14e"); + faMap.put("fa-caret-square-o-down", "\uf150"); + faMap.put("fa-caret-square-o-up", "\uf151"); + faMap.put("fa-caret-square-o-right", "\uf152"); + faMap.put("fa-eur", "\uf153"); + faMap.put("fa-gbp", "\uf154"); + faMap.put("fa-usd", "\uf155"); + faMap.put("fa-inr", "\uf156"); + faMap.put("fa-jpy", "\uf157"); + faMap.put("fa-rub", "\uf158"); + faMap.put("fa-krw", "\uf159"); + faMap.put("fa-btc", "\uf15a"); + faMap.put("fa-file", "\uf15b"); + faMap.put("fa-file-text", "\uf15c"); + faMap.put("fa-sort-alpha-asc", "\uf15d"); + faMap.put("fa-sort-alpha-desc", "\uf15e"); + faMap.put("fa-sort-amount-asc", "\uf160"); + faMap.put("fa-sort-amount-desc", "\uf161"); + faMap.put("fa-sort-numeric-asc", "\uf162"); + faMap.put("fa-sort-numeric-desc", "\uf163"); + faMap.put("fa-thumbs-up", "\uf164"); + faMap.put("fa-thumbs-down", "\uf165"); + faMap.put("fa-youtube-square", "\uf166"); + faMap.put("fa-youtube", "\uf167"); + faMap.put("fa-xing", "\uf168"); + faMap.put("fa-xing-square", "\uf169"); + faMap.put("fa-youtube-play", "\uf16a"); + faMap.put("fa-dropbox", "\uf16b"); + faMap.put("fa-stack-overflow", "\uf16c"); + faMap.put("fa-instagram", "\uf16d"); + faMap.put("fa-flickr", "\uf16e"); + faMap.put("fa-adn", "\uf170"); + faMap.put("fa-bitbucket", "\uf171"); + faMap.put("fa-bitbucket-square", "\uf172"); + faMap.put("fa-tumblr", "\uf173"); + faMap.put("fa-tumblr-square", "\uf174"); + faMap.put("fa-long-arrow-down", "\uf175"); + faMap.put("fa-long-arrow-up", "\uf176"); + faMap.put("fa-long-arrow-left", "\uf177"); + faMap.put("fa-long-arrow-right", "\uf178"); + faMap.put("fa-apple", "\uf179"); + faMap.put("fa-windows", "\uf17a"); + faMap.put("fa-android", "\uf17b"); + faMap.put("fa-linux", "\uf17c"); + faMap.put("fa-dribbble", "\uf17d"); + faMap.put("fa-skype", "\uf17e"); + faMap.put("fa-foursquare", "\uf180"); + faMap.put("fa-trello", "\uf181"); + faMap.put("fa-female", "\uf182"); + faMap.put("fa-male", "\uf183"); + faMap.put("fa-gittip", "\uf184"); + faMap.put("fa-sun-o", "\uf185"); + faMap.put("fa-moon-o", "\uf186"); + faMap.put("fa-archive", "\uf187"); + faMap.put("fa-bug", "\uf188"); + faMap.put("fa-vk", "\uf189"); + faMap.put("fa-weibo", "\uf18a"); + faMap.put("fa-renren", "\uf18b"); + faMap.put("fa-pagelines", "\uf18c"); + faMap.put("fa-stack-exchange", "\uf18d"); + faMap.put("fa-arrow-circle-o-right", "\uf18e"); + faMap.put("fa-arrow-circle-o-left", "\uf190"); + faMap.put("fa-caret-square-o-left", "\uf191"); + faMap.put("fa-dot-circle-o", "\uf192"); + faMap.put("fa-wheelchair", "\uf193"); + faMap.put("fa-vimeo-square", "\uf194"); + } + + public static Map getFaMap() + { + return faMap; + } + +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesomeText.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesomeText.java new file mode 100644 index 000000000..75a130c5a --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/FontAwesomeText.java @@ -0,0 +1,274 @@ +package com.beardedhen.androidbootstrap; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.LinearInterpolator; +import android.view.animation.RotateAnimation; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.R; + +public class FontAwesomeText extends FrameLayout { + + private static Typeface font; + private static Map faMap; + + private TextView tv; + + private static final String FA_ICON_QUESTION = "fa-question"; + + public enum AnimationSpeed + { + FAST, + MEDIUM, + SLOW; + } + + static{ + faMap = FontAwesome.getFaMap(); + } + + public FontAwesomeText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialise(attrs); + } + + public FontAwesomeText(Context context, AttributeSet attrs) { + super(context, attrs); + initialise(attrs); + } + + public FontAwesomeText(Context context) { + super(context); + initialise(null); + } + + + + + private void initialise( AttributeSet attrs ) + { + LayoutInflater inflator = (LayoutInflater)getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + //get font + readFont(getContext()); + + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FontAwesomeText); + + //inflate the view + View fontAwesomeTextView = inflator.inflate(R.layout.font_awesome_text, null, false); + tv = (TextView)fontAwesomeTextView.findViewById(R.id.lblText); + + String icon = ""; + float fontSize = 14.0f; + + //icon + if (a.getString(R.styleable.FontAwesomeText_fa_icon) != null) { + icon = a.getString(R.styleable.FontAwesomeText_fa_icon); + } + + //font size + if (a.getString(R.styleable.FontAwesomeText_android_textSize) != null) { + + String xmlProvidedSize = attrs.getAttributeValue( + "http://schemas.android.com/apk/res/android", "textSize"); + final Pattern PATTERN_FONT_SIZE = Pattern + .compile("([0-9]+[.]?[0-9]*)sp"); + Matcher m = PATTERN_FONT_SIZE.matcher(xmlProvidedSize); + + if (m.find()) { + if (m.groupCount() == 1) { + fontSize = Float.valueOf(m.group(1)); + } + } + } + + //text colour + if(a.getString(R.styleable.FontAwesomeText_android_textColor) != null){ + tv.setTextColor(a.getColor(R.styleable.FontAwesomeText_android_textColor, R.color.bbutton_inverse)); + } + + setIcon(icon); + + tv.setTypeface(font); + tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); + + a.recycle(); + addView(fontAwesomeTextView); + } + + private static void readFont(Context context) + { + + if(font == null){ + try { + font = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + } catch (Exception e) { + Log.e("BButton", "Could not get typeface because " + e.getMessage()); + font = Typeface.DEFAULT; + } + } + + } + + + /** + * Used to start flashing a FontAwesomeText item + * @param context the current applications context + * @param forever whether the item should flash repeatedly or just once + * @param speed how fast the item should flash, chose between FontAwesomeText.AnimationSpeed.SLOW / + * FontAwesomeText.AnimationSpeed.MEDIUM / FontAwesomeText.AnimationSpeed.FAST + */ + public void startFlashing(Context context, boolean forever, AnimationSpeed speed) + { + + Animation fadeIn = new AlphaAnimation(0, 1); + + //set up extra variables + fadeIn.setDuration(50); + fadeIn.setRepeatMode(Animation.REVERSE); + + //default repeat count is 0, however if user wants, set it up to be infinite + fadeIn.setRepeatCount(0); + if (forever){ + fadeIn.setRepeatCount(Animation.INFINITE); + } + + //default speed + fadeIn.setStartOffset(1000); + + //fast + if (speed.equals(AnimationSpeed.FAST)) + { + fadeIn.setStartOffset(200); + } + + //medium + if (speed.equals(AnimationSpeed.MEDIUM)) + { + fadeIn.setStartOffset(500); + } + + //set the new animation to a final animation + final Animation animation = fadeIn; + + //run the animation - used to work correctly on older devices + tv.postDelayed(new Runnable() { + @Override + public void run() { + tv.startAnimation(animation); + } + }, 100); + } + + + /** + * Used to start rotating a FontAwesomeText item + * @param context the current applications context + * @param clockwise true for clockwise, false for anti clockwise spinning + * @param speed how fast the item should flash, chose between FontAwesomeText.AnimationSpeed.SLOW / + * FontAwesomeText.AnimationSpeed.MEDIUM / FontAwesomeText.AnimationSpeed.FAST + */ + public void startRotate(Context context, boolean clockwise, AnimationSpeed speed) + { + Animation rotate; + + //set up the rotation animation + if (clockwise){ + rotate = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f); + } else { + rotate = new RotateAnimation(360, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f); + } + + //set up some extra variables + rotate.setRepeatCount(Animation.INFINITE); + rotate.setInterpolator(new LinearInterpolator()); + rotate.setStartOffset(0); + rotate.setRepeatMode(Animation.RESTART); + + //defaults + rotate.setDuration(2000); + + //fast + if (speed.equals(AnimationSpeed.FAST)) + { + rotate.setDuration(500); + } + + //medium + if (speed.equals(AnimationSpeed.MEDIUM)) + { + rotate.setDuration(1000); + } + + //send the new animation to a final animation + final Animation animation = rotate; + + //run the animation - used to work correctly on older devices + tv.postDelayed(new Runnable() { + @Override + public void run() { + tv.startAnimation(animation); + } + }, 100); + + } + + + /** + * Used to stop animating any FontAwesomeText item + */ + public void stopAnimation(){ + //stop the animation + tv.clearAnimation(); + } + + + /** + * Used to set the icon for a FontAwesomeText item + * @param faIcon - String value for the icon as per http://fortawesome.github.io/Font-Awesome/cheatsheet/ + */ + public void setIcon(String faIcon) { + + String icon = faMap.get(faIcon); + + if (icon == null) + { + icon = faMap.get(FA_ICON_QUESTION); + } + + tv.setText(icon); + } + + /** + * Used to set the text color of the underlying text view. + * @param color - Integer value representing a color resource. + */ + public void setTextColor(int color) { + tv.setTextColor(color); + } + + /** + * Used to set the text size of the underlying text view. + * @param unit - Integer value representing a unit size + * @param size - Float value representing text size + */ + public void setTextSize(int unit, float size) { + tv.setTextSize(unit, size); + } + +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/AutoResizeTextView.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/AutoResizeTextView.java new file mode 100644 index 000000000..be6f328d3 --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/AutoResizeTextView.java @@ -0,0 +1,303 @@ +package com.beardedhen.androidbootstrap.utils; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.RectF; +import android.os.Build; +import android.text.Layout.Alignment; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.SparseIntArray; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * + * Code from user M-WaJeEh on StackOverflow at + * http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds/17782522#17782522 + * + */ + +public class AutoResizeTextView extends TextView { +private interface SizeTester { + /** + * + * @param suggestedSize + * Size of text to be tested + * @param availableSpace + * available space in which text must fit + * @return an integer < 0 if after applying {@code suggestedSize} to + * text, it takes less space than {@code availableSpace}, > 0 + * otherwise + */ + public int onTestSize(int suggestedSize, RectF availableSpace); +} + +private RectF mTextRect = new RectF(); + +private RectF mAvailableSpaceRect; + +private SparseIntArray mTextCachedSizes; + +private TextPaint mPaint; + +private float mMaxTextSize; + +private float mSpacingMult = 1.0f; + +private float mSpacingAdd = 0.0f; + +private float mMinTextSize = 20; + +private int mWidthLimit; + +private static final int NO_LINE_LIMIT = -1; +private int mMaxLines; + +private boolean mEnableSizeCache = true; +private boolean mInitiallized; + +public AutoResizeTextView(Context context) { + super(context); + initialize(); +} + +public AutoResizeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); +} + +public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(); +} + +private void initialize() { + mPaint = new TextPaint(getPaint()); + mMaxTextSize = getTextSize(); + mAvailableSpaceRect = new RectF(); + mTextCachedSizes = new SparseIntArray(); + if (mMaxLines == 0) { + // no value was assigned during construction + mMaxLines = NO_LINE_LIMIT; + } + mInitiallized = true; +} + +@Override +public void setText(final CharSequence text, BufferType type) { + super.setText(text, type); + adjustTextSize(text.toString()); +} + +@Override +public void setTextSize(float size) { + mMaxTextSize = size; + mTextCachedSizes.clear(); + adjustTextSize(getText().toString()); +} + +@Override +public void setMaxLines(int maxlines) { + super.setMaxLines(maxlines); + mMaxLines = maxlines; + reAdjust(); +} + +public int getMaxLines() { + return mMaxLines; +} + +@Override +public void setSingleLine() { + super.setSingleLine(); + mMaxLines = 1; + reAdjust(); +} + +@Override +public void setSingleLine(boolean singleLine) { + super.setSingleLine(singleLine); + if (singleLine) { + mMaxLines = 1; + } else { + mMaxLines = NO_LINE_LIMIT; + } + reAdjust(); +} + +@Override +public void setLines(int lines) { + super.setLines(lines); + mMaxLines = lines; + reAdjust(); +} + +@Override +public void setTextSize(int unit, float size) { + Context c = getContext(); + Resources r; + + if (c == null) + r = Resources.getSystem(); + else + r = c.getResources(); + mMaxTextSize = TypedValue.applyDimension(unit, size, + r.getDisplayMetrics()); + mTextCachedSizes.clear(); + adjustTextSize(getText().toString()); +} + +@Override +public void setLineSpacing(float add, float mult) { + super.setLineSpacing(add, mult); + mSpacingMult = mult; + mSpacingAdd = add; +} + +/** + * Set the lower text size limit and invalidate the view + * + * @param minTextSize + */ +public void setMinTextSize(float minTextSize) { + mMinTextSize = minTextSize; + reAdjust(); +} + +private void reAdjust() { + adjustTextSize(getText().toString()); +} + +private void adjustTextSize(String string) { + if (!mInitiallized) { + return; + } + int startSize = (int) mMinTextSize; + int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() + - getCompoundPaddingTop(); + mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() + - getCompoundPaddingRight(); + mAvailableSpaceRect.right = mWidthLimit; + mAvailableSpaceRect.bottom = heightLimit; + super.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + efficientTextSizeSearch(startSize, (int) mMaxTextSize, + mSizeTester, mAvailableSpaceRect)); +} + +private final SizeTester mSizeTester = new SizeTester() { + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public int onTestSize(int suggestedSize, RectF availableSPace) { + mPaint.setTextSize(suggestedSize); + String text = getText().toString(); + boolean singleline = getMaxLines() == 1; + if (singleline) { + mTextRect.bottom = mPaint.getFontSpacing(); + mTextRect.right = mPaint.measureText(text); + } else { + StaticLayout layout = new StaticLayout(text, mPaint, + mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult, + mSpacingAdd, true); + // return early if we have more lines + if (getMaxLines() != NO_LINE_LIMIT + && layout.getLineCount() > getMaxLines()) { + return 1; + } + mTextRect.bottom = layout.getHeight(); + int maxWidth = -1; + for (int i = 0; i < layout.getLineCount(); i++) { + if (maxWidth < layout.getLineWidth(i)) { + maxWidth = (int) layout.getLineWidth(i); + } + } + mTextRect.right = maxWidth; + } + + mTextRect.offsetTo(0, 0); + if (availableSPace.contains(mTextRect)) { + // may be too small, don't worry we will find the best match + return -1; + } else { + // too big + return 1; + } + } +}; + +/** + * Enables or disables size caching, enabling it will improve performance + * where you are animating a value inside TextView. This stores the font + * size against getText().length() Be careful though while enabling it as 0 + * takes more space than 1 on some fonts and so on. + * + * @param enable + * enable font size caching + */ +public void enableSizeCache(boolean enable) { + mEnableSizeCache = enable; + mTextCachedSizes.clear(); + adjustTextSize(getText().toString()); +} + +private int efficientTextSizeSearch(int start, int end, + SizeTester sizeTester, RectF availableSpace) { + if (!mEnableSizeCache) { + return binarySearch(start, end, sizeTester, availableSpace); + } + String text = getText().toString(); + int key = text == null ? 0 : text.length(); + int size = mTextCachedSizes.get(key); + if (size != 0) { + return size; + } + size = binarySearch(start, end, sizeTester, availableSpace); + mTextCachedSizes.put(key, size); + return size; +} + +private static int binarySearch(int start, int end, SizeTester sizeTester, + RectF availableSpace) { + int lastBest = start; + int lo = start; + int hi = end - 1; + int mid = 0; + while (lo <= hi) { + mid = (lo + hi) >>> 1; + int midValCmp = sizeTester.onTestSize(mid, availableSpace); + if (midValCmp < 0) { + lastBest = lo; + lo = mid + 1; + } else if (midValCmp > 0) { + hi = mid - 1; + lastBest = hi; + } else { + return mid; + } + } + // make sure to return last best + // this is what should always be returned + return lastBest; + +} + +@Override +protected void onTextChanged(final CharSequence text, final int start, + final int before, final int after) { + super.onTextChanged(text, start, before, after); + reAdjust(); +} + +@Override +protected void onSizeChanged(int width, int height, int oldwidth, + int oldheight) { + mTextCachedSizes.clear(); + super.onSizeChanged(width, height, oldwidth, oldheight); + if (width != oldwidth || height != oldheight) { + reAdjust(); + } +} +} diff --git a/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/ImageUtils.java b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/ImageUtils.java new file mode 100644 index 000000000..3eefa9366 --- /dev/null +++ b/libraries/AndroidBootstrap/src/com/beardedhen/androidbootstrap/utils/ImageUtils.java @@ -0,0 +1,77 @@ +package com.beardedhen.androidbootstrap.utils; + + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Bitmap.Config; +import android.graphics.PorterDuff.Mode; + +public class ImageUtils +{ + + public static Bitmap getCircleBitmap(Bitmap bitmap) + { + return getCircleBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight()); + } + + public static Bitmap getCircleBitmap(Bitmap bitmap, int width, int height) + { + Bitmap croppedBitmap = scaleCenterCrop(bitmap, width, height); + Bitmap output = Bitmap.createBitmap(width, height, Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + + final Rect rect = new Rect(0, 0, width, height); + final RectF rectF = new RectF(rect); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + + int radius = 0; + if(width > height) + { + radius = height / 2; + } + else + { + radius = width / 2; + } + + canvas.drawCircle(width / 2, height / 2, radius, paint); + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + canvas.drawBitmap(croppedBitmap, rect, rect, paint); + + return output; + } + + public static Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) + { + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + + float xScale = (float) newWidth / sourceWidth; + float yScale = (float) newHeight / sourceHeight; + float scale = Math.max(xScale, yScale); + + float scaledWidth = scale * sourceWidth; + float scaledHeight = scale * sourceHeight; + + float left = (newWidth - scaledWidth) / 2; + float top = (newHeight - scaledHeight) / 2; + + RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); + + Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig()); + Canvas canvas = new Canvas(dest); + canvas.drawBitmap(source, null, targetRect, null); + + return dest; + } +} \ No newline at end of file diff --git a/libraries/StickyListHeaders/.gitignore b/libraries/StickyListHeaders/.gitignore new file mode 100644 index 000000000..71d84cca6 --- /dev/null +++ b/libraries/StickyListHeaders/.gitignore @@ -0,0 +1,98 @@ +###Android### + +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Eclipse project files +.classpath +.project + + +###Eclipse### + +*.pydevproject +.project +.metadata +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +###Maven### + +target/ + + +###Gradle### + +.gradle/ +build/ + + +###IntelliJ### + +*.iml +*.ipr +*.iws +.idea/ + + +###Actionscript### + +# Build and Release Folders +bin/ +bin-debug/ +bin-release/ + +# Project property files +.actionScriptProperties +.flexProperties +.settings/ +.project + +###OSX### + +.DS_Store + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + + diff --git a/libraries/StickyListHeaders/LICENSE b/libraries/StickyListHeaders/LICENSE new file mode 100644 index 000000000..37ec93a14 --- /dev/null +++ b/libraries/StickyListHeaders/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/libraries/StickyListHeaders/README.md b/libraries/StickyListHeaders/README.md new file mode 100644 index 000000000..2cfff2991 --- /dev/null +++ b/libraries/StickyListHeaders/README.md @@ -0,0 +1,203 @@ +StickyListHeaders +================= +StickyListHeaders is an Android library that makes it easy to integrate section headers in your `ListView`. These section headers stick to the top like in the new People app of Android 4.0 Ice Cream Sandwich. This behavior is also found in lists with sections on iOS devices. This library can also be used without the sticky functionality if you just want section headers. + +StickyListHeaders actively supports android versions 2.3 (gingerbread) and above. +That said, it works all the way down to 2.1 but is not actively tested or working perfectly. + +Here is a short gif showing the functionality you get with this library: + +![alt text](https://github.com/emilsjolander/StickyListHeaders/raw/master/demo.gif "Demo gif") + + +Goal +---- +The goal of this project is to deliver a high performance replacement to `ListView`. You should with minimal effort and time be able to add section headers to a list. This should be done via a simple to use API without any special features. This library will always priorities general use cases over special ones. This means that the library will add very few public methods to the standard `ListView` and will not try to work for every use case. While I will want to support even narrow use cases I will not do so if it compromises the API or any other feature. + + +Installing +--------------- +###Gradle +Add the following gradle dependency exchanging `x.x.x` for the latest release. +```groovy +dependencies { + compile 'se.emilsjolander:stickylistheaders:x.x.x' +} +``` + +###Cloning +First of all you will have to clone the library. +```shell +git clone https://github.com/emilsjolander/StickyListHeaders.git +``` + +Now that you have the library you will have to import it into Android Studio. +In Android Studio navigate the menus like this. +``` +File -> Import Project ... +``` +In the following dialog navigate to StickyListHeaders which you cloned to your computer in the previous steps and select the `build.gradle`. + +Getting Started +--------------- +Ok lets start with your activities or fragments xml file. It might look something like this. +```xml + +``` + +Now in your activities `onCreate()` or your fragments `onCreateView()` you would want to do something like this +```java +StickyListHeadersListView stickyList = (StickyListHeadersListView) findViewById(R.id.list); +MyAdapter adapter = new MyAdapter(this); +stickyList.setAdapter(adapter); +``` + +`MyAdapter` in the above example would look something like this if your list was a list of countries where each header was for a letter in the alphabet. +```java +public class MyAdapter extends BaseAdapter implements StickyListHeadersAdapter { + + private String[] countries; + private LayoutInflater inflater; + + public TestBaseAdapter(Context context) { + inflater = LayoutInflater.from(context); + countries = context.getResources().getStringArray(R.array.countries); + } + + @Override + public int getCount() { + return countries.length; + } + + @Override + public Object getItem(int position) { + return countries[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + + if (convertView == null) { + holder = new ViewHolder(); + convertView = inflater.inflate(R.layout.test_list_item_layout, parent, false); + holder.text = (TextView) convertView.findViewById(R.id.text); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + holder.text.setText(countries[position]); + + return convertView; + } + + @Override + public View getHeaderView(int position, View convertView, ViewGroup parent) { + HeaderViewHolder holder; + if (convertView == null) { + holder = new HeaderViewHolder(); + convertView = inflater.inflate(R.layout.header, parent, false); + holder.text = (TextView) convertView.findViewById(R.id.text); + convertView.setTag(holder); + } else { + holder = (HeaderViewHolder) convertView.getTag(); + } + //set header text as first char in name + String headerText = "" + countries[position].subSequence(0, 1).charAt(0); + holder.text.setText(headerText); + return convertView; + } + + @Override + public long getHeaderId(int position) { + //return the first character of the country as ID because this is what headers are based upon + return countries[position].subSequence(0, 1).charAt(0); + } + + class HeaderViewHolder { + TextView text; + } + + class ViewHolder { + TextView text; + } + +} +``` + +That's it! Look through the API docs below to get know about things to customize and if you have any problems getting started please open an issue as it probably means the getting started guide need some improvement! + +Upgrading from 1.x versions +--------------------------- +First of all the package name has changed from `com.emilsjolander.components.stickylistheaders` -> `se.emilsjolander.stickylistheaders` so update all your imports and xml files using StickyListHeaders! + +If you are Upgrading from a version prior to 2.x you might run into the following problems. +1. `StickyListHeadersListView` is no longer a `ListView` subclass. This means that it cannot be passed into a method expecting a ListView. You can retrieve an instance of the `ListView` via `getWrappedList()` but use this with caution as things will probably break if you start setting things directly on that list. +2. Because `StickyListHeadersListView` is no longer a `ListView` it does not support all the methods. I have implemented delegate methods for all the usual methods and gladly accept pull requests for more. + +API +--- +###StickyListHeadersAdapter +```java +public interface StickyListHeadersAdapter extends ListAdapter { + View getHeaderView(int position, View convertView, ViewGroup parent); + long getHeaderId(int position); +} +``` +Your adapter must implement this interface to function with `StickyListHeadersListView`. +`getHeaderId()` must return a unique integer for every section. A valid implementation for a list with alphabetical sections is the return the char value of the section that `position` is a part of. + +`getHeaderView()` works exactly like `getView()` in a regular `ListAdapter`. + + +###StickyListHeadersListView +Headers are sticky by default but that can easily be changed with this setter. There is of course also a matching getter for the sticky property. +```java +public void setAreHeadersSticky(boolean areHeadersSticky); +public boolean areHeadersSticky(); +``` + +A `OnHeaderClickListener` is the header version of OnItemClickListener. This is the setter for it and the interface of the listener. The currentlySticky boolean flag indicated if the header that was clicked was sticking to the top at the time it was clicked. +```java +public void setOnHeaderClickListener(OnHeaderClickListener listener); + +public interface OnHeaderClickListener { + public void onHeaderClick(StickyListHeadersListView l, View header, int itemPosition, long headerId, boolean currentlySticky); +} +``` + +A `OnStickyHeaderOffsetChangedListener` is a Listener used for listening to when the sticky header slides out of the screen. The offset parameter will slowly grow to be the same size as the headers height. Use the listeners callback to transform the header in any way you see fit, the standard android contacts app dims the text for example. +```java +public void setOnStickyHeaderOffsetChangedListener(OnStickyHeaderOffsetChangedListener listener); + +public interface OnStickyHeaderOffsetChangedListener { + public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset); +} +``` + +Here are two methods added to the API for inspecting the children of the underlying `ListView`. I could not override the normal `getChildAt()` and `getChildCount()` methods as that would mess up the underlying measurement system of the `FrameLayout` wrapping the `ListView`. +```java +public View getListChildAt(int index); +public int getListChildCount(); +``` + +This is a setter and getter for an internal attribute that controls if the list should be drawn under the stuck header. The default value is true. If you do not want to see the list scroll under your header you will want to set this attribute to `false`. +```java +public void setDrawingListUnderStickyHeader(boolean drawingListUnderStickyHeader); +public boolean isDrawingListUnderStickyHeader(); +``` + +Contributing +------------ +Contributions are very welcome. Now that this library has grown in popularity i have a hard time keeping upp with all the issues while tending to a multitude of other projects as well as school. So if you find a bug in the library or want a feature and think you can fix it yourself, fork + pull request and i will greatly appreciate it! + +I love getting pull requests for new features as well as bugs. However, when it comes to new features please also explain the use case and way you think the library should include it. If you don't want to start coding a feature without knowing if the feature will have chance of being included, open an issue and we can discuss the feature! diff --git a/libraries/StickyListHeaders/library/.gitignore b/libraries/StickyListHeaders/library/.gitignore new file mode 100644 index 000000000..f8b92c3aa --- /dev/null +++ b/libraries/StickyListHeaders/library/.gitignore @@ -0,0 +1,2 @@ +.gradle +build diff --git a/libraries/StickyListHeaders/library/AndroidManifest.xml b/libraries/StickyListHeaders/library/AndroidManifest.xml new file mode 100644 index 000000000..328dcc65c --- /dev/null +++ b/libraries/StickyListHeaders/library/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/libraries/StickyListHeaders/library/build.gradle b/libraries/StickyListHeaders/library/build.gradle new file mode 100644 index 000000000..21050fc98 --- /dev/null +++ b/libraries/StickyListHeaders/library/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion '19.0.0' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/libraries/StickyListHeaders/library/build.xml b/libraries/StickyListHeaders/library/build.xml new file mode 100644 index 000000000..2f6f323a2 --- /dev/null +++ b/libraries/StickyListHeaders/library/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/StickyListHeaders/library/proguard-project.txt b/libraries/StickyListHeaders/library/proguard-project.txt new file mode 100644 index 000000000..f2fe1559a --- /dev/null +++ b/libraries/StickyListHeaders/library/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/libraries/StickyListHeaders/library/project.properties b/libraries/StickyListHeaders/library/project.properties new file mode 100644 index 000000000..91d2b0246 --- /dev/null +++ b/libraries/StickyListHeaders/library/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true diff --git a/libraries/StickyListHeaders/library/res/values/attrs.xml b/libraries/StickyListHeaders/library/res/values/attrs.xml new file mode 100644 index 000000000..8d09a9c1b --- /dev/null +++ b/libraries/StickyListHeaders/library/res/values/attrs.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java new file mode 100644 index 000000000..e67de0dbf --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java @@ -0,0 +1,225 @@ +package se.emilsjolander.stickylistheaders; + +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Checkable; +import android.widget.ListAdapter; + +/** + * A {@link ListAdapter} which wraps a {@link StickyListHeadersAdapter} and + * automatically handles wrapping the result of + * {@link StickyListHeadersAdapter#getView(int, android.view.View, android.view.ViewGroup)} + * and + * {@link StickyListHeadersAdapter#getHeaderView(int, android.view.View, android.view.ViewGroup)} + * appropriately. + * + * @author Jake Wharton (jakewharton@gmail.com) + */ +class AdapterWrapper extends BaseAdapter implements StickyListHeadersAdapter { + + interface OnHeaderClickListener { + public void onHeaderClick(View header, int itemPosition, long headerId); + } + + final StickyListHeadersAdapter mDelegate; + private final List mHeaderCache = new LinkedList(); + private final Context mContext; + private Drawable mDivider; + private int mDividerHeight; + private OnHeaderClickListener mOnHeaderClickListener; + private DataSetObserver mDataSetObserver = new DataSetObserver() { + + @Override + public void onInvalidated() { + mHeaderCache.clear(); + AdapterWrapper.super.notifyDataSetInvalidated(); + } + + @Override + public void onChanged() { + AdapterWrapper.super.notifyDataSetChanged(); + } + }; + + AdapterWrapper(Context context, + StickyListHeadersAdapter delegate) { + this.mContext = context; + this.mDelegate = delegate; + delegate.registerDataSetObserver(mDataSetObserver); + } + + void setDivider(Drawable divider, int dividerHeight) { + this.mDivider = divider; + this.mDividerHeight = dividerHeight; + notifyDataSetChanged(); + } + + @Override + public boolean areAllItemsEnabled() { + return mDelegate.areAllItemsEnabled(); + } + + @Override + public boolean isEnabled(int position) { + return mDelegate.isEnabled(position); + } + + @Override + public int getCount() { + return mDelegate.getCount(); + } + + @Override + public Object getItem(int position) { + return mDelegate.getItem(position); + } + + @Override + public long getItemId(int position) { + return mDelegate.getItemId(position); + } + + @Override + public boolean hasStableIds() { + return mDelegate.hasStableIds(); + } + + @Override + public int getItemViewType(int position) { + return mDelegate.getItemViewType(position); + } + + @Override + public int getViewTypeCount() { + return mDelegate.getViewTypeCount(); + } + + @Override + public boolean isEmpty() { + return mDelegate.isEmpty(); + } + + /** + * Will recycle header from {@link WrapperView} if it exists + */ + private void recycleHeaderIfExists(WrapperView wv) { + View header = wv.mHeader; + if (header != null) { + // reset the headers visibility when adding it to the cache + header.setVisibility(View.VISIBLE); + mHeaderCache.add(header); + } + } + + /** + * Get a header view. This optionally pulls a header from the supplied + * {@link WrapperView} and will also recycle the divider if it exists. + */ + private View configureHeader(WrapperView wv, final int position) { + View header = wv.mHeader == null ? popHeader() : wv.mHeader; + header = mDelegate.getHeaderView(position, header, wv); + if (header == null) { + throw new NullPointerException("Header view must not be null."); + } + //if the header isn't clickable, the listselector will be drawn on top of the header + header.setClickable(true); + header.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if(mOnHeaderClickListener != null){ + long headerId = mDelegate.getHeaderId(position); + mOnHeaderClickListener.onHeaderClick(v, position, headerId); + } + } + }); + return header; + } + + private View popHeader() { + if(mHeaderCache.size() > 0) { + return mHeaderCache.remove(0); + } + return null; + } + + /** Returns {@code true} if the previous position has the same header ID. */ + private boolean previousPositionHasSameHeader(int position) { + return position != 0 + && mDelegate.getHeaderId(position) == mDelegate + .getHeaderId(position - 1); + } + + @Override + public WrapperView getView(int position, View convertView, ViewGroup parent) { + WrapperView wv = (convertView == null) ? new WrapperView(mContext) : (WrapperView) convertView; + View item = mDelegate.getView(position, wv.mItem, parent); + View header = null; + if (previousPositionHasSameHeader(position)) { + recycleHeaderIfExists(wv); + } else { + header = configureHeader(wv, position); + } + if((item instanceof Checkable) && !(wv instanceof CheckableWrapperView)) { + // Need to create Checkable subclass of WrapperView for ListView to work correctly + wv = new CheckableWrapperView(mContext); + } else if(!(item instanceof Checkable) && (wv instanceof CheckableWrapperView)) { + wv = new WrapperView(mContext); + } + wv.update(item, header, mDivider, mDividerHeight); + return wv; + } + + public void setOnHeaderClickListener(OnHeaderClickListener onHeaderClickListener){ + this.mOnHeaderClickListener = onHeaderClickListener; + } + + @Override + public boolean equals(Object o) { + return mDelegate.equals(o); + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return ((BaseAdapter) mDelegate).getDropDownView(position, convertView, parent); + } + + @Override + public int hashCode() { + return mDelegate.hashCode(); + } + + @Override + public void notifyDataSetChanged() { + ((BaseAdapter) mDelegate).notifyDataSetChanged(); + } + + @Override + public void notifyDataSetInvalidated() { + ((BaseAdapter) mDelegate).notifyDataSetInvalidated(); + } + + @Override + public String toString() { + return mDelegate.toString(); + } + + @Override + public View getHeaderView(int position, View convertView, ViewGroup parent) { + return mDelegate.getHeaderView(position, convertView, parent); + } + + @Override + public long getHeaderId(int position) { + return mDelegate.getHeaderId(position); + } + +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java new file mode 100644 index 000000000..5b0a83827 --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java @@ -0,0 +1,11 @@ +package se.emilsjolander.stickylistheaders; + +public class ApiLevelTooLowException extends RuntimeException { + + private static final long serialVersionUID = -5480068364264456757L; + + public ApiLevelTooLowException(int versionCode) { + super("Requires API level " + versionCode); + } + +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/CheckableWrapperView.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/CheckableWrapperView.java new file mode 100644 index 000000000..9039c3f5c --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/CheckableWrapperView.java @@ -0,0 +1,31 @@ +package se.emilsjolander.stickylistheaders; + +import android.content.Context; +import android.widget.Checkable; + +/** + * A WrapperView that implements the checkable interface + * + * @author Emil Sjölander + */ +class CheckableWrapperView extends WrapperView implements Checkable { + + public CheckableWrapperView(final Context context) { + super(context); + } + + @Override + public boolean isChecked() { + return ((Checkable) mItem).isChecked(); + } + + @Override + public void setChecked(final boolean checked) { + ((Checkable) mItem).setChecked(checked); + } + + @Override + public void toggle() { + setChecked(!isChecked()); + } +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java new file mode 100644 index 000000000..ff7e293af --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/SectionIndexerAdapterWrapper.java @@ -0,0 +1,32 @@ +package se.emilsjolander.stickylistheaders; + +import android.content.Context; +import android.widget.SectionIndexer; + +class SectionIndexerAdapterWrapper extends + AdapterWrapper implements SectionIndexer { + + final SectionIndexer mSectionIndexerDelegate; + + SectionIndexerAdapterWrapper(Context context, + StickyListHeadersAdapter delegate) { + super(context, delegate); + mSectionIndexerDelegate = (SectionIndexer) delegate; + } + + @Override + public int getPositionForSection(int section) { + return mSectionIndexerDelegate.getPositionForSection(section); + } + + @Override + public int getSectionForPosition(int position) { + return mSectionIndexerDelegate.getSectionForPosition(position); + } + + @Override + public Object[] getSections() { + return mSectionIndexerDelegate.getSections(); + } + +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersAdapter.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersAdapter.java new file mode 100644 index 000000000..8b80b71f1 --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersAdapter.java @@ -0,0 +1,38 @@ +package se.emilsjolander.stickylistheaders; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListAdapter; + +public interface StickyListHeadersAdapter extends ListAdapter { + /** + * Get a View that displays the header data at the specified position in the + * set. You can either create a View manually or inflate it from an XML layout + * file. + * + * @param position + * The position of the item within the adapter's data set of the item whose + * header view we want. + * @param convertView + * The old view to reuse, if possible. Note: You should check that this view is + * non-null and of an appropriate type before using. If it is not possible to + * convert this view to display the correct data, this method can create a new + * view. + * @param parent + * The parent that this view will eventually be attached to. + * @return + * A View corresponding to the data at the specified position. + */ + View getHeaderView(int position, View convertView, ViewGroup parent); + + /** + * Get the header id associated with the specified position in the list. + * + * @param position + * The position of the item within the adapter's data set whose header id we + * want. + * @return + * The id of the header at the specified position. + */ + long getHeaderId(int position); +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java new file mode 100644 index 000000000..476f6cfad --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java @@ -0,0 +1,925 @@ +package se.emilsjolander.stickylistheaders; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseBooleanArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.SectionIndexer; + +import se.emilsjolander.stickylistheaders.WrapperViewList.LifeCycleListener; + +/** + * Even though this is a FrameLayout subclass we it is called a ListView. This + * is because of 2 reasons. 1. It acts like as ListView 2. It used to be a + * ListView subclass and i did not was to change to name causing compatibility + * errors. + * + * @author Emil Sjölander + */ +public class StickyListHeadersListView extends FrameLayout { + + public interface OnHeaderClickListener { + public void onHeaderClick(StickyListHeadersListView l, View header, + int itemPosition, long headerId, boolean currentlySticky); + } + + /** + * Notifies the listener when the sticky headers top offset has changed. + */ + public interface OnStickyHeaderOffsetChangedListener { + /** + * @param l The view parent + * @param header The currently sticky header being offset. + * This header is not guaranteed to have it's measurements set. + * It is however guaranteed that this view has been measured, + * therefor you should user getMeasured* methods instead of + * get* methods for determining the view's size. + * @param offset The amount the sticky header is offset by towards to top of the screen. + */ + public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset); + } + + /* --- Children --- */ + private WrapperViewList mList; + private View mHeader; + + /* --- Header state --- */ + private Long mHeaderId; + // used to not have to call getHeaderId() all the time + private Integer mHeaderPosition; + private Integer mHeaderOffset; + + /* --- Delegates --- */ + private OnScrollListener mOnScrollListenerDelegate; + private AdapterWrapper mAdapter; + + /* --- Settings --- */ + private boolean mAreHeadersSticky = true; + private boolean mClippingToPadding = true; + private boolean mIsDrawingListUnderStickyHeader = true; + private int mPaddingLeft = 0; + private int mPaddingTop = 0; + private int mPaddingRight = 0; + private int mPaddingBottom = 0; + + /* --- Other --- */ + private OnHeaderClickListener mOnHeaderClickListener; + private OnStickyHeaderOffsetChangedListener mOnStickyHeaderOffsetChangedListener; + private AdapterWrapperDataSetObserver mDataSetObserver; + private Drawable mDivider; + private int mDividerHeight; + + public StickyListHeadersListView(Context context) { + this(context, null); + } + + public StickyListHeadersListView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public StickyListHeadersListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Initialize the wrapped list + mList = new WrapperViewList(context); + + // null out divider, dividers are handled by adapter so they look good with headers + mDivider = mList.getDivider(); + mDividerHeight = mList.getDividerHeight(); + mList.setDivider(null); + mList.setDividerHeight(0); + + mList.setVerticalScrollBarEnabled(isVerticalScrollBarEnabled()); + mList.setHorizontalScrollBarEnabled(isHorizontalScrollBarEnabled()); + + if (attrs != null) { + TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.StickyListHeadersListView, 0, 0); + + try { + // -- View attributes -- + int padding = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_padding, 0); + mPaddingLeft = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingLeft, padding); + mPaddingTop = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingTop, padding); + mPaddingRight = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingRight, padding); + mPaddingBottom = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingBottom, padding); + + setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, + mPaddingBottom); + + // Set clip to padding on the list and reset value to default on + // wrapper + mClippingToPadding = a.getBoolean(R.styleable.StickyListHeadersListView_android_clipToPadding, true); + super.setClipToPadding(true); + mList.setClipToPadding(mClippingToPadding); + + // -- ListView attributes -- + mList.setFadingEdgeLength(a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_fadingEdgeLength, + mList.getVerticalFadingEdgeLength())); + final int fadingEdge = a.getInt(R.styleable.StickyListHeadersListView_android_requiresFadingEdge, 0); + if (fadingEdge == 0x00001000) { + mList.setVerticalFadingEdgeEnabled(false); + mList.setHorizontalFadingEdgeEnabled(true); + } else if (fadingEdge == 0x00002000) { + mList.setVerticalFadingEdgeEnabled(true); + mList.setHorizontalFadingEdgeEnabled(false); + } else { + mList.setVerticalFadingEdgeEnabled(false); + mList.setHorizontalFadingEdgeEnabled(false); + } + mList.setCacheColorHint(a + .getColor(R.styleable.StickyListHeadersListView_android_cacheColorHint, mList.getCacheColorHint())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mList.setChoiceMode(a.getInt(R.styleable.StickyListHeadersListView_android_choiceMode, + mList.getChoiceMode())); + } + mList.setDrawSelectorOnTop(a.getBoolean(R.styleable.StickyListHeadersListView_android_drawSelectorOnTop, false)); + mList.setFastScrollEnabled(a.getBoolean(R.styleable.StickyListHeadersListView_android_fastScrollEnabled, + mList.isFastScrollEnabled())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mList.setFastScrollAlwaysVisible(a.getBoolean( + R.styleable.StickyListHeadersListView_android_fastScrollAlwaysVisible, + mList.isFastScrollAlwaysVisible())); + } + + mList.setScrollBarStyle(a.getInt(R.styleable.StickyListHeadersListView_android_scrollbarStyle, 0)); + + if (a.hasValue(R.styleable.StickyListHeadersListView_android_listSelector)) { + mList.setSelector(a.getDrawable(R.styleable.StickyListHeadersListView_android_listSelector)); + } + + mList.setScrollingCacheEnabled(a.getBoolean(R.styleable.StickyListHeadersListView_android_scrollingCache, + mList.isScrollingCacheEnabled())); + + if (a.hasValue(R.styleable.StickyListHeadersListView_android_divider)) { + mDivider = a.getDrawable(R.styleable.StickyListHeadersListView_android_divider); + } + + mDividerHeight = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_dividerHeight, + mDividerHeight); + + // -- StickyListHeaders attributes -- + mAreHeadersSticky = a.getBoolean(R.styleable.StickyListHeadersListView_hasStickyHeaders, true); + mIsDrawingListUnderStickyHeader = a.getBoolean( + R.styleable.StickyListHeadersListView_isDrawingListUnderStickyHeader, + true); + } finally { + a.recycle(); + } + } + + // attach some listeners to the wrapped list + mList.setLifeCycleListener(new WrapperViewListLifeCycleListener()); + mList.setOnScrollListener(new WrapperListScrollListener()); + + addView(mList); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + measureHeader(mHeader); + } + + private void ensureHeaderHasCorrectLayoutParams(View header) { + ViewGroup.LayoutParams lp = header.getLayoutParams(); + if (lp == null) { + lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } else if (lp.height == LayoutParams.MATCH_PARENT) { + lp.height = LayoutParams.WRAP_CONTENT; + } + header.setLayoutParams(lp); + } + + private void measureHeader(View header) { + if (header != null) { + final int width = getMeasuredWidth() - mPaddingLeft - mPaddingRight; + final int parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + width, MeasureSpec.EXACTLY); + final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.UNSPECIFIED); + measureChild(header, parentWidthMeasureSpec, + parentHeightMeasureSpec); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, + int bottom) { + mList.layout(0, 0, mList.getMeasuredWidth(), getHeight()); + if (mHeader != null) { + MarginLayoutParams lp = (MarginLayoutParams) mHeader + .getLayoutParams(); + int headerTop = lp.topMargin + + (mClippingToPadding ? mPaddingTop : 0); + // The left parameter must for some reason be set to 0. + // I think it should be set to mPaddingLeft but apparently not + mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth() + + mPaddingLeft, headerTop + mHeader.getMeasuredHeight()); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + // Only draw the list here. + // The header should be drawn right after the lists children are drawn. + // This is done so that the header is above the list items + // but below the list decorators (scroll bars etc). + drawChild(canvas, mList, 0); + } + + // Reset values tied the header. also remove header form layout + // This is called in response to the data set or the adapter changing + private void clearHeader() { + if (mHeader != null) { + removeView(mHeader); + mHeader = null; + mHeaderId = null; + mHeaderPosition = null; + mHeaderOffset = null; + + // reset the top clipping length + mList.setTopClippingLength(0); + updateHeaderVisibilities(); + } + } + + private void updateOrClearHeader(int firstVisiblePosition) { + final int adapterCount = mAdapter == null ? 0 : mAdapter.getCount(); + if (adapterCount == 0 || !mAreHeadersSticky) { + return; + } + + final int headerViewCount = mList.getHeaderViewsCount(); + final int realFirstVisibleItem = firstVisiblePosition - headerViewCount; + + // It is not a mistake to call getFirstVisiblePosition() here. + // Most of the time getFixedFirstVisibleItem() should be called + // but that does not work great together with getChildAt() + final boolean doesListHaveChildren = mList.getChildCount() != 0; + final boolean isFirstViewBelowTop = doesListHaveChildren && mList + .getFirstVisiblePosition() == 0 + && mList.getChildAt(0).getTop() > (mClippingToPadding ? mPaddingTop : 0); + final boolean isFirstVisibleItemOutsideAdapterRange = realFirstVisibleItem > adapterCount - 1 + || realFirstVisibleItem < 0; + if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange + || isFirstViewBelowTop) { + clearHeader(); + return; + } + + updateHeader(realFirstVisibleItem); + } + + private void updateHeader(int firstVisiblePosition) { + + // check if there is a new header should be sticky + if (mHeaderPosition == null || mHeaderPosition != firstVisiblePosition) { + mHeaderPosition = firstVisiblePosition; + final long headerId = mAdapter.getHeaderId(firstVisiblePosition); + if (mHeaderId == null || mHeaderId != headerId) { + mHeaderId = headerId; + final View header = mAdapter.getHeaderView(mHeaderPosition, + mHeader, this); + if (mHeader != header) { + if (header == null) { + throw new NullPointerException("header may not be null"); + } + swapHeader(header); + } + + ensureHeaderHasCorrectLayoutParams(mHeader); + measureHeader(mHeader); + + // Reset mHeaderOffset to null ensuring + // that it will be set on the header and + // not skipped for performance reasons. + mHeaderOffset = null; + } + } + + int headerOffset = 0; + + // Calculate new header offset + // Skip looking at the first view. it never matters because it always + // results in a headerOffset = 0 + int headerBottom = mHeader.getMeasuredHeight() + + (mClippingToPadding ? mPaddingTop : 0); + for (int i = 0; i < mList.getChildCount(); i++) { + final View child = mList.getChildAt(i); + final boolean doesChildHaveHeader = child instanceof WrapperView + && ((WrapperView) child).hasHeader(); + final boolean isChildFooter = mList.containsFooterView(child); + if (child.getTop() >= (mClippingToPadding ? mPaddingTop : 0) + && (doesChildHaveHeader || isChildFooter)) { + headerOffset = Math.min(child.getTop() - headerBottom, 0); + break; + } + } + + setHeaderOffet(headerOffset); + + if (!mIsDrawingListUnderStickyHeader) { + mList.setTopClippingLength(mHeader.getMeasuredHeight() + + mHeaderOffset); + } + + updateHeaderVisibilities(); + } + + private void swapHeader(View newHeader) { + if (mHeader != null) { + removeView(mHeader); + } + mHeader = newHeader; + addView(mHeader); + mHeader.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (mOnHeaderClickListener != null) { + mOnHeaderClickListener.onHeaderClick( + StickyListHeadersListView.this, mHeader, + mHeaderPosition, mHeaderId, true); + } + } + + }); + } + + // hides the headers in the list under the sticky header. + // Makes sure the other ones are showing + private void updateHeaderVisibilities() { + int top; + if (mHeader != null) { + top = mHeader.getMeasuredHeight() + + (mHeaderOffset != null ? mHeaderOffset : 0); + } else { + top = mClippingToPadding ? mPaddingTop : 0; + } + int childCount = mList.getChildCount(); + for (int i = 0; i < childCount; i++) { + + // ensure child is a wrapper view + View child = mList.getChildAt(i); + if (!(child instanceof WrapperView)) { + continue; + } + + // ensure wrapper view child has a header + WrapperView wrapperViewChild = (WrapperView) child; + if (!wrapperViewChild.hasHeader()) { + continue; + } + + // update header views visibility + View childHeader = wrapperViewChild.mHeader; + if (wrapperViewChild.getTop() < top) { + if (childHeader.getVisibility() != View.INVISIBLE) { + childHeader.setVisibility(View.INVISIBLE); + } + } else { + if (childHeader.getVisibility() != View.VISIBLE) { + childHeader.setVisibility(View.VISIBLE); + } + } + } + } + + // Wrapper around setting the header offset in different ways depending on + // the API version + @SuppressLint("NewApi") + private void setHeaderOffet(int offset) { + if (mHeaderOffset == null || mHeaderOffset != offset) { + mHeaderOffset = offset; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mHeader.setTranslationY(mHeaderOffset); + } else { + MarginLayoutParams params = (MarginLayoutParams) mHeader + .getLayoutParams(); + params.topMargin = mHeaderOffset; + mHeader.setLayoutParams(params); + } + if (mOnStickyHeaderOffsetChangedListener != null) { + mOnStickyHeaderOffsetChangedListener + .onStickyHeaderOffsetChanged(this, mHeader, -mHeaderOffset); + } + } + } + + private class AdapterWrapperDataSetObserver extends DataSetObserver { + + @Override + public void onChanged() { + clearHeader(); + } + + @Override + public void onInvalidated() { + clearHeader(); + } + + } + + private class WrapperListScrollListener implements OnScrollListener { + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + if (mOnScrollListenerDelegate != null) { + mOnScrollListenerDelegate.onScroll(view, firstVisibleItem, + visibleItemCount, totalItemCount); + } + updateOrClearHeader(mList.getFixedFirstVisibleItem()); + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (mOnScrollListenerDelegate != null) { + mOnScrollListenerDelegate.onScrollStateChanged(view, + scrollState); + } + } + + } + + private class WrapperViewListLifeCycleListener implements LifeCycleListener { + + @Override + public void onDispatchDrawOccurred(Canvas canvas) { + // onScroll is not called often at all before froyo + // therefor we need to update the header here as well. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { + updateOrClearHeader(mList.getFixedFirstVisibleItem()); + } + if (mHeader != null) { + if (mClippingToPadding) { + canvas.save(); + canvas.clipRect(0, mPaddingTop, getRight(), getBottom()); + drawChild(canvas, mHeader, 0); + canvas.restore(); + } else { + drawChild(canvas, mHeader, 0); + } + } + } + + } + + private class AdapterWrapperHeaderClickHandler implements + AdapterWrapper.OnHeaderClickListener { + + @Override + public void onHeaderClick(View header, int itemPosition, long headerId) { + mOnHeaderClickListener.onHeaderClick( + StickyListHeadersListView.this, header, itemPosition, + headerId, false); + } + + } + + private boolean isStartOfSection(int position) { + return position == 0 + || mAdapter.getHeaderId(position) != mAdapter + .getHeaderId(position - 1); + } + + private int getHeaderOverlap(int position) { + boolean isStartOfSection = isStartOfSection(position); + if (!isStartOfSection) { + View header = mAdapter.getHeaderView(position, null, mList); + if (header == null) { + throw new NullPointerException("header may not be null"); + } + ensureHeaderHasCorrectLayoutParams(header); + measureHeader(header); + return header.getMeasuredHeight(); + } + return 0; + } + + /* ---------- StickyListHeaders specific API ---------- */ + + public void setAreHeadersSticky(boolean areHeadersSticky) { + mAreHeadersSticky = areHeadersSticky; + if (!areHeadersSticky) { + clearHeader(); + } else { + updateOrClearHeader(mList.getFixedFirstVisibleItem()); + } + // invalidating the list will trigger dispatchDraw() + mList.invalidate(); + } + + public boolean areHeadersSticky() { + return mAreHeadersSticky; + } + + /** + * Use areHeadersSticky() method instead + */ + @Deprecated + public boolean getAreHeadersSticky() { + return areHeadersSticky(); + } + + public void setDrawingListUnderStickyHeader( + boolean drawingListUnderStickyHeader) { + mIsDrawingListUnderStickyHeader = drawingListUnderStickyHeader; + // reset the top clipping length + mList.setTopClippingLength(0); + } + + public boolean isDrawingListUnderStickyHeader() { + return mIsDrawingListUnderStickyHeader; + } + + public void setOnHeaderClickListener(OnHeaderClickListener listener) { + mOnHeaderClickListener = listener; + if (mAdapter != null) { + if (mOnHeaderClickListener != null) { + mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler()); + } else { + mAdapter.setOnHeaderClickListener(null); + } + } + } + + public void setOnStickyHeaderOffsetChangedListener(OnStickyHeaderOffsetChangedListener listener) { + mOnStickyHeaderOffsetChangedListener = listener; + } + + public View getListChildAt(int index) { + return mList.getChildAt(index); + } + + public int getListChildCount() { + return mList.getChildCount(); + } + + /** + * Use the method with extreme caution!! Changing any values on the + * underlying ListView might break everything. + * + * @return the ListView backing this view. + */ + public ListView getWrappedList() { + return mList; + } + + /* ---------- ListView delegate methods ---------- */ + + public void setAdapter(StickyListHeadersAdapter adapter) { + if (adapter == null) { + mList.setAdapter(null); + clearHeader(); + return; + } + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + } + + if (adapter instanceof SectionIndexer) { + mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter); + } else { + mAdapter = new AdapterWrapper(getContext(), adapter); + } + mDataSetObserver = new AdapterWrapperDataSetObserver(); + mAdapter.registerDataSetObserver(mDataSetObserver); + + if (mOnHeaderClickListener != null) { + mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler()); + } else { + mAdapter.setOnHeaderClickListener(null); + } + + mAdapter.setDivider(mDivider, mDividerHeight); + + mList.setAdapter(mAdapter); + clearHeader(); + } + + public StickyListHeadersAdapter getAdapter() { + return mAdapter == null ? null : mAdapter.mDelegate; + } + + public void setDivider(Drawable divider) { + mDivider = divider; + if (mAdapter != null) { + mAdapter.setDivider(mDivider, mDividerHeight); + } + } + + public void setDividerHeight(int dividerHeight) { + mDividerHeight = dividerHeight; + if (mAdapter != null) { + mAdapter.setDivider(mDivider, mDividerHeight); + } + } + + public Drawable getDivider() { + return mDivider; + } + + public int getDividerHeight() { + return mDividerHeight; + } + + public void setOnScrollListener(OnScrollListener onScrollListener) { + mOnScrollListenerDelegate = onScrollListener; + } + + public void setOnItemClickListener(OnItemClickListener listener) { + mList.setOnItemClickListener(listener); + } + + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + mList.setOnItemLongClickListener(listener); + } + + public void addHeaderView(View v, Object data, boolean isSelectable) { + mList.addHeaderView(v, data, isSelectable); + } + + public void addHeaderView(View v) { + mList.addHeaderView(v); + } + + public void removeHeaderView(View v) { + mList.removeHeaderView(v); + } + + public int getHeaderViewsCount() { + return mList.getHeaderViewsCount(); + } + + public void addFooterView(View v) { + mList.addFooterView(v); + } + + public void removeFooterView(View v) { + mList.removeFooterView(v); + } + + public int getFooterViewsCount() { + return mList.getFooterViewsCount(); + } + + public void setEmptyView(View v) { + mList.setEmptyView(v); + } + + public View getEmptyView() { + return mList.getEmptyView(); + } + + @Override + public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { + mList.setVerticalScrollBarEnabled(verticalScrollBarEnabled); + } + + @Override + public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) { + mList.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public void smoothScrollBy(int distance, int duration) { + requireSdkVersion(Build.VERSION_CODES.FROYO); + mList.smoothScrollBy(distance, duration); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void smoothScrollByOffset(int offset) { + requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); + mList.smoothScrollByOffset(offset); + } + + @SuppressLint("NewApi") + @TargetApi(Build.VERSION_CODES.FROYO) + public void smoothScrollToPosition(int position) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + mList.smoothScrollToPosition(position); + } else { + int offset = mAdapter == null ? 0 : getHeaderOverlap(position); + offset -= mClippingToPadding ? 0 : mPaddingTop; + mList.smoothScrollToPositionFromTop(position, offset); + } + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public void smoothScrollToPosition(int position, int boundPosition) { + requireSdkVersion(Build.VERSION_CODES.FROYO); + mList.smoothScrollToPosition(position, boundPosition); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void smoothScrollToPositionFromTop(int position, int offset) { + requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); + offset += mAdapter == null ? 0 : getHeaderOverlap(position); + offset -= mClippingToPadding ? 0 : mPaddingTop; + mList.smoothScrollToPositionFromTop(position, offset); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void smoothScrollToPositionFromTop(int position, int offset, + int duration) { + requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); + offset += mAdapter == null ? 0 : getHeaderOverlap(position); + offset -= mClippingToPadding ? 0 : mPaddingTop; + mList.smoothScrollToPositionFromTop(position, offset, duration); + } + + public void setSelection(int position) { + setSelectionFromTop(position, 0); + } + + public void setSelectionAfterHeaderView() { + mList.setSelectionAfterHeaderView(); + } + + public void setSelectionFromTop(int position, int y) { + y += mAdapter == null ? 0 : getHeaderOverlap(position); + y -= mClippingToPadding ? 0 : mPaddingTop; + mList.setSelectionFromTop(position, y); + } + + public void setSelector(Drawable sel) { + mList.setSelector(sel); + } + + public void setSelector(int resID) { + mList.setSelector(resID); + } + + public int getFirstVisiblePosition() { + return mList.getFirstVisiblePosition(); + } + + public int getLastVisiblePosition() { + return mList.getLastVisiblePosition(); + } + + public void setChoiceMode(int choiceMode) { + mList.setChoiceMode(choiceMode); + } + + public void setItemChecked(int position, boolean value) { + mList.setItemChecked(position, value); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public int getCheckedItemCount() { + requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); + return mList.getCheckedItemCount(); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public long[] getCheckedItemIds() { + requireSdkVersion(Build.VERSION_CODES.FROYO); + return mList.getCheckedItemIds(); + } + + public int getCheckedItemPosition() { + return mList.getCheckedItemPosition(); + } + + public SparseBooleanArray getCheckedItemPositions() { + return mList.getCheckedItemPositions(); + } + + public int getCount() { + return mList.getCount(); + } + + public Object getItemAtPosition(int position) { + return mList.getItemAtPosition(position); + } + + public long getItemIdAtPosition(int position) { + return mList.getItemIdAtPosition(position); + } + + @Override + public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) { + mList.setOnCreateContextMenuListener(l); + } + + @Override + public boolean showContextMenu() { + return mList.showContextMenu(); + } + + public void invalidateViews() { + mList.invalidateViews(); + } + + @Override + public void setClipToPadding(boolean clipToPadding) { + if (mList != null) { + mList.setClipToPadding(clipToPadding); + } + mClippingToPadding = clipToPadding; + } + + @Override + public void setPadding(int left, int top, int right, int bottom) { + mPaddingLeft = left; + mPaddingTop = top; + mPaddingRight = right; + mPaddingBottom = bottom; + + if (mList != null) { + mList.setPadding(left, top, right, bottom); + } + super.setPadding(0, 0, 0, 0); + requestLayout(); + } + + /* + * Overrides an @hide method in View + */ + protected void recomputePadding() { + setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); + } + + @Override + public int getPaddingLeft() { + return mPaddingLeft; + } + + @Override + public int getPaddingTop() { + return mPaddingTop; + } + + @Override + public int getPaddingRight() { + return mPaddingRight; + } + + @Override + public int getPaddingBottom() { + return mPaddingBottom; + } + + public void setFastScrollEnabled(boolean fastScrollEnabled) { + mList.setFastScrollEnabled(fastScrollEnabled); + } + + /** + * @throws ApiLevelTooLowException on pre-Honeycomb device. + * @see android.widget.AbsListView#setFastScrollAlwaysVisible(boolean) + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void setFastScrollAlwaysVisible(boolean alwaysVisible) { + requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); + mList.setFastScrollAlwaysVisible(alwaysVisible); + } + + /** + * @return true if the fast scroller will always show. False on pre-Honeycomb devices. + * @see android.widget.AbsListView#isFastScrollAlwaysVisible() + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public boolean isFastScrollAlwaysVisible() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + return false; + } + return mList.isFastScrollAlwaysVisible(); + } + + public void setScrollBarStyle(int style) { + mList.setScrollBarStyle(style); + } + + public int getScrollBarStyle() { + return mList.getScrollBarStyle(); + } + + private void requireSdkVersion(int versionCode) { + if (Build.VERSION.SDK_INT < versionCode) { + throw new ApiLevelTooLowException(versionCode); + } + } + + public int getPositionForView(View view) { + return mList.getPositionForView(view); + } + +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperView.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperView.java new file mode 100644 index 000000000..f51416c1c --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperView.java @@ -0,0 +1,150 @@ +package se.emilsjolander.stickylistheaders; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +/** + * + * the view that wrapps a divider header and a normal list item. The listview sees this as 1 item + * + * @author Emil Sjölander + */ +public class WrapperView extends ViewGroup { + + View mItem; + Drawable mDivider; + int mDividerHeight; + View mHeader; + int mItemTop; + + WrapperView(Context c) { + super(c); + } + + public boolean hasHeader() { + return mHeader != null; + } + + public View getItem() { + return mItem; + } + + public View getHeader() { + return mHeader; + } + + void update(View item, View header, Drawable divider, int dividerHeight) { + + //every wrapperview must have a list item + if (item == null) { + throw new NullPointerException("List view item must not be null."); + } + + //only remove the current item if it is not the same as the new item. this can happen if wrapping a recycled view + if (this.mItem != item) { + removeView(this.mItem); + this.mItem = item; + final ViewParent parent = item.getParent(); + if(parent != null && parent != this) { + if(parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(item); + } + } + addView(item); + } + + //same logik as above but for the header + if (this.mHeader != header) { + if (this.mHeader != null) { + removeView(this.mHeader); + } + this.mHeader = header; + if (header != null) { + addView(header); + } + } + + if (this.mDivider != divider) { + this.mDivider = divider; + this.mDividerHeight = dividerHeight; + invalidate(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, + MeasureSpec.EXACTLY); + int measuredHeight = 0; + + //measure header or divider. when there is a header visible it acts as the divider + if (mHeader != null) { + ViewGroup.LayoutParams params = mHeader.getLayoutParams(); + if (params != null && params.height > 0) { + mHeader.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY)); + } else { + mHeader.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + } + measuredHeight += mHeader.getMeasuredHeight(); + } else if (mDivider != null) { + measuredHeight += mDividerHeight; + } + + //measure item + ViewGroup.LayoutParams params = mItem.getLayoutParams(); + if (params != null && params.height > 0) { + mItem.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY)); + } else { + mItem.measure(childWidthMeasureSpec, + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + } + measuredHeight += mItem.getMeasuredHeight(); + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + + l = 0; + t = 0; + r = getWidth(); + b = getHeight(); + + if (mHeader != null) { + int headerHeight = mHeader.getMeasuredHeight(); + mHeader.layout(l, t, r, headerHeight); + mItemTop = headerHeight; + mItem.layout(l, headerHeight, r, b); + } else if (mDivider != null) { + mDivider.setBounds(l, t, r, mDividerHeight); + mItemTop = mDividerHeight; + mItem.layout(l, mDividerHeight, r, b); + } else { + mItemTop = t; + mItem.layout(l, t, r, b); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mHeader == null && mDivider != null) { + // Drawable.setBounds() does not seem to work pre-honeycomb. So have + // to do this instead + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + canvas.clipRect(0, 0, getWidth(), mDividerHeight); + } + mDivider.draw(canvas); + } + } +} diff --git a/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java new file mode 100644 index 000000000..3d68e98db --- /dev/null +++ b/libraries/StickyListHeaders/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java @@ -0,0 +1,175 @@ +package se.emilsjolander.stickylistheaders; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.widget.AbsListView; +import android.widget.ListView; + +class WrapperViewList extends ListView { + + interface LifeCycleListener { + void onDispatchDrawOccurred(Canvas canvas); + } + + private LifeCycleListener mLifeCycleListener; + private List mFooterViews; + private int mTopClippingLength; + private Rect mSelectorRect = new Rect();// for if reflection fails + private Field mSelectorPositionField; + private boolean mClippingToPadding = true; + + public WrapperViewList(Context context) { + super(context); + + // Use reflection to be able to change the size/position of the list + // selector so it does not come under/over the header + try { + Field selectorRectField = AbsListView.class.getDeclaredField("mSelectorRect"); + selectorRectField.setAccessible(true); + mSelectorRect = (Rect) selectorRectField.get(this); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mSelectorPositionField = AbsListView.class.getDeclaredField("mSelectorPosition"); + mSelectorPositionField.setAccessible(true); + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override + public boolean performItemClick(View view, int position, long id) { + if (view instanceof WrapperView) { + view = ((WrapperView) view).mItem; + } + return super.performItemClick(view, position, id); + } + + private void positionSelectorRect() { + if (!mSelectorRect.isEmpty()) { + int selectorPosition = getSelectorPosition(); + if (selectorPosition >= 0) { + int firstVisibleItem = getFixedFirstVisibleItem(); + View v = getChildAt(selectorPosition - firstVisibleItem); + if (v instanceof WrapperView) { + WrapperView wrapper = ((WrapperView) v); + mSelectorRect.top = wrapper.getTop() + wrapper.mItemTop; + } + } + } + } + + private int getSelectorPosition() { + if (mSelectorPositionField == null) { // not all supported andorid + // version have this variable + for (int i = 0; i < getChildCount(); i++) { + if (getChildAt(i).getBottom() == mSelectorRect.bottom) { + return i + getFixedFirstVisibleItem(); + } + } + } else { + try { + return mSelectorPositionField.getInt(this); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return -1; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + positionSelectorRect(); + if (mTopClippingLength != 0) { + canvas.save(); + Rect clipping = canvas.getClipBounds(); + clipping.top = mTopClippingLength; + canvas.clipRect(clipping); + super.dispatchDraw(canvas); + canvas.restore(); + } else { + super.dispatchDraw(canvas); + } + mLifeCycleListener.onDispatchDrawOccurred(canvas); + } + + void setLifeCycleListener(LifeCycleListener lifeCycleListener) { + mLifeCycleListener = lifeCycleListener; + } + + @Override + public void addFooterView(View v) { + super.addFooterView(v); + if (mFooterViews == null) { + mFooterViews = new ArrayList(); + } + mFooterViews.add(v); + } + + @Override + public boolean removeFooterView(View v) { + if (super.removeFooterView(v)) { + mFooterViews.remove(v); + return true; + } + return false; + } + + boolean containsFooterView(View v) { + if (mFooterViews == null) { + return false; + } + return mFooterViews.contains(v); + } + + void setTopClippingLength(int topClipping) { + mTopClippingLength = topClipping; + } + + int getFixedFirstVisibleItem() { + int firstVisibleItem = getFirstVisiblePosition(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + return firstVisibleItem; + } + + // first getFirstVisiblePosition() reports items + // outside the view sometimes on old versions of android + for (int i = 0; i < getChildCount(); i++) { + if (getChildAt(i).getBottom() >= 0) { + firstVisibleItem += i; + break; + } + } + + // work around to fix bug with firstVisibleItem being to high + // because list view does not take clipToPadding=false into account + // on old versions of android + if (!mClippingToPadding && getPaddingTop() > 0 && firstVisibleItem > 0) { + if (getChildAt(0).getTop() > 0) { + firstVisibleItem -= 1; + } + } + + return firstVisibleItem; + } + + @Override + public void setClipToPadding(boolean clipToPadding) { + mClippingToPadding = clipToPadding; + super.setClipToPadding(clipToPadding); + } + +} diff --git a/libraries/zxing/AndroidManifest.xml b/libraries/zxing/AndroidManifest.xml new file mode 100644 index 000000000..ef720bdb6 --- /dev/null +++ b/libraries/zxing/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/libraries/zxing/build.gradle b/libraries/zxing/build.gradle new file mode 100644 index 000000000..21050fc98 --- /dev/null +++ b/libraries/zxing/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion '19.0.0' + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } +} diff --git a/libraries/zxing/project.properties b/libraries/zxing/project.properties new file mode 100644 index 000000000..91d2b0246 --- /dev/null +++ b/libraries/zxing/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-19 +android.library=true diff --git a/OpenPGP-Keychain/src/com/google/zxing/BarcodeFormat.java b/libraries/zxing/src/com/google/zxing/BarcodeFormat.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/BarcodeFormat.java rename to libraries/zxing/src/com/google/zxing/BarcodeFormat.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/Binarizer.java b/libraries/zxing/src/com/google/zxing/Binarizer.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/Binarizer.java rename to libraries/zxing/src/com/google/zxing/Binarizer.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/BinaryBitmap.java b/libraries/zxing/src/com/google/zxing/BinaryBitmap.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/BinaryBitmap.java rename to libraries/zxing/src/com/google/zxing/BinaryBitmap.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/ChecksumException.java b/libraries/zxing/src/com/google/zxing/ChecksumException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/ChecksumException.java rename to libraries/zxing/src/com/google/zxing/ChecksumException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/DecodeHintType.java b/libraries/zxing/src/com/google/zxing/DecodeHintType.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/DecodeHintType.java rename to libraries/zxing/src/com/google/zxing/DecodeHintType.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/EncodeHintType.java b/libraries/zxing/src/com/google/zxing/EncodeHintType.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/EncodeHintType.java rename to libraries/zxing/src/com/google/zxing/EncodeHintType.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/FormatException.java b/libraries/zxing/src/com/google/zxing/FormatException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/FormatException.java rename to libraries/zxing/src/com/google/zxing/FormatException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/LuminanceSource.java b/libraries/zxing/src/com/google/zxing/LuminanceSource.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/LuminanceSource.java rename to libraries/zxing/src/com/google/zxing/LuminanceSource.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/NotFoundException.java b/libraries/zxing/src/com/google/zxing/NotFoundException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/NotFoundException.java rename to libraries/zxing/src/com/google/zxing/NotFoundException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/Reader.java b/libraries/zxing/src/com/google/zxing/Reader.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/Reader.java rename to libraries/zxing/src/com/google/zxing/Reader.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/ReaderException.java b/libraries/zxing/src/com/google/zxing/ReaderException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/ReaderException.java rename to libraries/zxing/src/com/google/zxing/ReaderException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/Result.java b/libraries/zxing/src/com/google/zxing/Result.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/Result.java rename to libraries/zxing/src/com/google/zxing/Result.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/ResultMetadataType.java b/libraries/zxing/src/com/google/zxing/ResultMetadataType.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/ResultMetadataType.java rename to libraries/zxing/src/com/google/zxing/ResultMetadataType.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/ResultPoint.java b/libraries/zxing/src/com/google/zxing/ResultPoint.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/ResultPoint.java rename to libraries/zxing/src/com/google/zxing/ResultPoint.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/ResultPointCallback.java b/libraries/zxing/src/com/google/zxing/ResultPointCallback.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/ResultPointCallback.java rename to libraries/zxing/src/com/google/zxing/ResultPointCallback.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/Writer.java b/libraries/zxing/src/com/google/zxing/Writer.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/Writer.java rename to libraries/zxing/src/com/google/zxing/Writer.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/WriterException.java b/libraries/zxing/src/com/google/zxing/WriterException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/WriterException.java rename to libraries/zxing/src/com/google/zxing/WriterException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/BitArray.java b/libraries/zxing/src/com/google/zxing/common/BitArray.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/BitArray.java rename to libraries/zxing/src/com/google/zxing/common/BitArray.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/BitMatrix.java b/libraries/zxing/src/com/google/zxing/common/BitMatrix.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/BitMatrix.java rename to libraries/zxing/src/com/google/zxing/common/BitMatrix.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/BitSource.java b/libraries/zxing/src/com/google/zxing/common/BitSource.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/BitSource.java rename to libraries/zxing/src/com/google/zxing/common/BitSource.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/CharacterSetECI.java b/libraries/zxing/src/com/google/zxing/common/CharacterSetECI.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/CharacterSetECI.java rename to libraries/zxing/src/com/google/zxing/common/CharacterSetECI.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/Collections.java b/libraries/zxing/src/com/google/zxing/common/Collections.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/Collections.java rename to libraries/zxing/src/com/google/zxing/common/Collections.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/Comparator.java b/libraries/zxing/src/com/google/zxing/common/Comparator.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/Comparator.java rename to libraries/zxing/src/com/google/zxing/common/Comparator.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/DecoderResult.java b/libraries/zxing/src/com/google/zxing/common/DecoderResult.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/DecoderResult.java rename to libraries/zxing/src/com/google/zxing/common/DecoderResult.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/DefaultGridSampler.java b/libraries/zxing/src/com/google/zxing/common/DefaultGridSampler.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/DefaultGridSampler.java rename to libraries/zxing/src/com/google/zxing/common/DefaultGridSampler.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/DetectorResult.java b/libraries/zxing/src/com/google/zxing/common/DetectorResult.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/DetectorResult.java rename to libraries/zxing/src/com/google/zxing/common/DetectorResult.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/ECI.java b/libraries/zxing/src/com/google/zxing/common/ECI.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/ECI.java rename to libraries/zxing/src/com/google/zxing/common/ECI.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/GlobalHistogramBinarizer.java b/libraries/zxing/src/com/google/zxing/common/GlobalHistogramBinarizer.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/GlobalHistogramBinarizer.java rename to libraries/zxing/src/com/google/zxing/common/GlobalHistogramBinarizer.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/GridSampler.java b/libraries/zxing/src/com/google/zxing/common/GridSampler.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/GridSampler.java rename to libraries/zxing/src/com/google/zxing/common/GridSampler.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/HybridBinarizer.java b/libraries/zxing/src/com/google/zxing/common/HybridBinarizer.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/HybridBinarizer.java rename to libraries/zxing/src/com/google/zxing/common/HybridBinarizer.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/PerspectiveTransform.java b/libraries/zxing/src/com/google/zxing/common/PerspectiveTransform.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/PerspectiveTransform.java rename to libraries/zxing/src/com/google/zxing/common/PerspectiveTransform.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/StringUtils.java b/libraries/zxing/src/com/google/zxing/common/StringUtils.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/StringUtils.java rename to libraries/zxing/src/com/google/zxing/common/StringUtils.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/libraries/zxing/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java rename to libraries/zxing/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/detector/WhiteRectangleDetector.java b/libraries/zxing/src/com/google/zxing/common/detector/WhiteRectangleDetector.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/detector/WhiteRectangleDetector.java rename to libraries/zxing/src/com/google/zxing/common/detector/WhiteRectangleDetector.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/GenericGF.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGF.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/GenericGF.java rename to libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGF.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java rename to libraries/zxing/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java rename to libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java rename to libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java rename to libraries/zxing/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/multi/ByQuadrantReader.java b/libraries/zxing/src/com/google/zxing/multi/ByQuadrantReader.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/multi/ByQuadrantReader.java rename to libraries/zxing/src/com/google/zxing/multi/ByQuadrantReader.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java b/libraries/zxing/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java rename to libraries/zxing/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/multi/MultipleBarcodeReader.java b/libraries/zxing/src/com/google/zxing/multi/MultipleBarcodeReader.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/multi/MultipleBarcodeReader.java rename to libraries/zxing/src/com/google/zxing/multi/MultipleBarcodeReader.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java rename to libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java b/libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java rename to libraries/zxing/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/QRCodeWriter.java b/libraries/zxing/src/com/google/zxing/qrcode/QRCodeWriter.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/QRCodeWriter.java rename to libraries/zxing/src/com/google/zxing/qrcode/QRCodeWriter.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DataBlock.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataBlock.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DataBlock.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/DataBlock.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DataMask.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DataMask.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DataMask.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/DataMask.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/FormatInformation.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/FormatInformation.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/FormatInformation.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Mode.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Mode.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Mode.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/Mode.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Version.java b/libraries/zxing/src/com/google/zxing/qrcode/decoder/Version.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/decoder/Version.java rename to libraries/zxing/src/com/google/zxing/qrcode/decoder/Version.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/AlignmentPattern.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPattern.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/AlignmentPattern.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPattern.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/Detector.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/Detector.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/Detector.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/Detector.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPattern.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPattern.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPattern.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPattern.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java rename to libraries/zxing/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/BlockPair.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/BlockPair.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/BlockPair.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/BlockPair.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/ByteMatrix.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/ByteMatrix.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/ByteMatrix.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/ByteMatrix.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/Encoder.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/Encoder.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/Encoder.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/Encoder.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/MaskUtil.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MaskUtil.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/MaskUtil.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/MaskUtil.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/MatrixUtil.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/MatrixUtil.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/MatrixUtil.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/MatrixUtil.java diff --git a/OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/QRCode.java b/libraries/zxing/src/com/google/zxing/qrcode/encoder/QRCode.java similarity index 100% rename from OpenPGP-Keychain/src/com/google/zxing/qrcode/encoder/QRCode.java rename to libraries/zxing/src/com/google/zxing/qrcode/encoder/QRCode.java diff --git a/settings.gradle b/settings.gradle index 2e582798c..5602503ee 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,6 @@ include ':OpenPGP-Keychain' include ':libraries:ActionBarSherlock' include ':libraries:HtmlTextView' -include ':libraries:pinned-section-listview:library' \ No newline at end of file +include ':libraries:StickyListHeaders:library' +include ':libraries:zxing' +include ':libraries:AndroidBootstrap'