├── .gitignore
├── .ruby-version
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Resources
└── icons.sketch
│ ├── Data
│ ├── QuickLook
│ ├── Preview.png
│ └── Thumbnail.png
│ ├── fonts
│ └── version
├── UVDemo
├── .classpath
├── .idea
│ ├── .name
│ ├── compiler.xml
│ ├── copyright
│ │ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── gradle.xml
│ ├── libraries
│ │ ├── commons_codec_1_8.xml
│ │ ├── httpcore_4_0_1.xml
│ │ ├── signpost_commonshttp4_1_2_1_2.xml
│ │ ├── signpost_core_1_2_1_2.xml
│ │ └── support_v4_19_0_1.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── scopes
│ │ └── scope_settings.xml
│ ├── vcs.xml
│ └── workspace.xml
├── AndroidManifest.xml
├── build.gradle
├── ic_launcher-web.png
├── proguard-rules.pro
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── activity_main.xml
│ ├── menu
│ │ └── main.xml
│ ├── values-sw600dp
│ │ └── dimens.xml
│ ├── values-sw720dp-land
│ │ └── dimens.xml
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
├── settings.gradle
└── src
│ └── com
│ └── uservoice
│ └── uvdemo
│ └── MainActivity.java
├── UserVoiceSDK
├── .idea
│ ├── .name
│ ├── compiler.xml
│ ├── copyright
│ │ └── profiles_settings.xml
│ ├── encodings.xml
│ ├── gradle.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── scopes
│ │ └── scope_settings.xml
│ ├── vcs.xml
│ └── workspace.xml
├── AndroidManifest.xml
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ic_launcher-web.png
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ ├── uv_add.png
│ │ ├── uv_article.png
│ │ ├── uv_comment.png
│ │ ├── uv_contact.png
│ │ ├── uv_heart.png
│ │ └── uv_idea.png
│ ├── drawable-ldpi
│ │ ├── uv_add.png
│ │ ├── uv_article.png
│ │ ├── uv_comment.png
│ │ ├── uv_contact.png
│ │ ├── uv_heart.png
│ │ └── uv_idea.png
│ ├── drawable-mdpi
│ │ ├── uv_add.png
│ │ ├── uv_article.png
│ │ ├── uv_comment.png
│ │ ├── uv_contact.png
│ │ ├── uv_heart.png
│ │ └── uv_idea.png
│ ├── drawable-xhdpi
│ │ ├── uv_add.png
│ │ ├── uv_article.png
│ │ ├── uv_comment.png
│ │ ├── uv_contact.png
│ │ ├── uv_heart.png
│ │ └── uv_idea.png
│ ├── drawable
│ │ ├── uv_admin_response.xml
│ │ ├── uv_clickable.xml
│ │ ├── uv_list_separator.xml
│ │ └── uv_list_separator_light.xml
│ ├── layout
│ │ ├── uv_article_layout.xml
│ │ ├── uv_comment_dialog.xml
│ │ ├── uv_comment_item.xml
│ │ ├── uv_contact_button_item.xml
│ │ ├── uv_contact_text_item.xml
│ │ ├── uv_divider.xml
│ │ ├── uv_header_item.xml
│ │ ├── uv_header_item_light.xml
│ │ ├── uv_header_item_light_no_padding.xml
│ │ ├── uv_idea_dialog.xml
│ │ ├── uv_idea_dialog_header.xml
│ │ ├── uv_idea_help_item.xml
│ │ ├── uv_instant_answer_item.xml
│ │ ├── uv_list_content.xml
│ │ ├── uv_loading_item.xml
│ │ ├── uv_password_dialog.xml
│ │ ├── uv_powered_by_item.xml
│ │ ├── uv_select_field_item.xml
│ │ ├── uv_signin_layout.xml
│ │ ├── uv_subscribe_dialog.xml
│ │ ├── uv_suggestion_item.xml
│ │ ├── uv_text_field_item.xml
│ │ └── uv_text_item.xml
│ ├── menu
│ │ ├── uv_forum.xml
│ │ └── uv_portal.xml
│ ├── values-af
│ │ └── Strings.xml
│ ├── values-bg
│ │ └── strings.xml
│ ├── values-ca
│ │ └── strings.xml
│ ├── values-cs
│ │ └── strings.xml
│ ├── values-da
│ │ └── strings.xml
│ ├── values-de
│ │ └── strings.xml
│ ├── values-el
│ │ └── strings.xml
│ ├── values-en-rGB
│ │ └── strings.xml
│ ├── values-en
│ │ └── strings.xml
│ ├── values-es
│ │ └── strings.xml
│ ├── values-fi
│ │ └── strings.xml
│ ├── values-fr
│ │ └── strings.xml
│ ├── values-hi
│ │ └── strings.xml
│ ├── values-hr
│ │ └── strings.xml
│ ├── values-hu
│ │ └── strings.xml
│ ├── values-in
│ │ └── strings.xml
│ ├── values-it
│ │ └── strings.xml
│ ├── values-ja
│ │ └── strings.xml
│ ├── values-ko
│ │ └── strings.xml
│ ├── values-lt
│ │ └── strings.xml
│ ├── values-lv
│ │ └── strings.xml
│ ├── values-nb-rNO
│ │ └── strings.xml
│ ├── values-nl
│ │ └── strings.xml
│ ├── values-pl
│ │ └── strings.xml
│ ├── values-pt-rBR
│ │ └── strings.xml
│ ├── values-pt-rPT
│ │ └── strings.xml
│ ├── values-ro
│ │ └── strings.xml
│ ├── values-ru
│ │ └── strings.xml
│ ├── values-sk
│ │ └── strings.xml
│ ├── values-sl
│ │ └── strings.xml
│ ├── values-sr
│ │ └── strings.xml
│ ├── values-sv
│ │ └── strings.xml
│ ├── values-sw600dp
│ │ └── dimens.xml
│ ├── values-sw720dp-land
│ │ └── dimens.xml
│ ├── values-th
│ │ └── strings.xml
│ ├── values-tl
│ │ └── strings.xml
│ ├── values-tr
│ │ └── strings.xml
│ ├── values-uk
│ │ └── strings.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-vi
│ │ └── strings.xml
│ ├── values-zh-rCN
│ │ └── strings.xml
│ ├── values-zh-rTW
│ │ └── strings.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── uservoice
│ └── uservoicesdk
│ ├── Config.java
│ ├── Session.java
│ ├── UserVoice.java
│ ├── activity
│ ├── ArticleActivity.java
│ ├── BaseActivity.java
│ ├── ContactActivity.java
│ ├── ForumActivity.java
│ ├── InstantAnswersActivity.java
│ ├── PortalActivity.java
│ ├── PostIdeaActivity.java
│ ├── SearchActivity.java
│ └── TopicActivity.java
│ ├── babayaga
│ ├── Babayaga.java
│ └── BabayagaTask.java
│ ├── compatibility
│ └── FragmentListActivity.java
│ ├── deflection
│ └── Deflection.java
│ ├── dialog
│ ├── ArticleDialogFragment.java
│ ├── CommentDialogFragment.java
│ ├── DialogFragmentBugfixed.java
│ ├── HelpfulDialogFragment.java
│ ├── PasswordDialogFragment.java
│ ├── SigninDialogFragment.java
│ ├── SubscribeDialogFragment.java
│ ├── SuggestionDialogFragment.java
│ └── UnhelpfulDialogFragment.java
│ ├── flow
│ ├── InitManager.java
│ ├── SigninCallback.java
│ └── SigninManager.java
│ ├── image
│ ├── DownloadImageTask.java
│ └── ImageCache.java
│ ├── model
│ ├── AccessToken.java
│ ├── AccessTokenResult.java
│ ├── Article.java
│ ├── Attachment.java
│ ├── BaseModel.java
│ ├── Category.java
│ ├── ClientConfig.java
│ ├── Comment.java
│ ├── CustomField.java
│ ├── Forum.java
│ ├── RequestToken.java
│ ├── Suggestion.java
│ ├── Ticket.java
│ ├── Topic.java
│ └── User.java
│ ├── rest
│ ├── Callback.java
│ ├── OkOAuthConsumer.java
│ ├── OkRequestAdapter.java
│ ├── RestMethod.java
│ ├── RestResult.java
│ ├── RestTask.java
│ └── RestTaskCallback.java
│ └── ui
│ ├── ContactAdapter.java
│ ├── DefaultCallback.java
│ ├── InstantAnswersAdapter.java
│ ├── LoadAllAdapter.java
│ ├── MixedSearchAdapter.java
│ ├── ModelAdapter.java
│ ├── PaginatedAdapter.java
│ ├── PaginationScrollListener.java
│ ├── PortalAdapter.java
│ ├── PostIdeaAdapter.java
│ ├── SearchAdapter.java
│ ├── SearchExpandListener.java
│ ├── SearchQueryListener.java
│ ├── SpinnerAdapter.java
│ ├── StickyFocusContainer.java
│ └── Utils.java
├── android.iml
├── build.gradle
├── demo.keystore
├── settings.gradle
└── strings.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | */bin/*
2 | */gen/*
3 | .metadata
4 | .DS_Store
5 | out
6 | .settings
7 | proguard_logs
8 | */build/*
9 | .gradle
10 | build
11 | local.properties
12 |
13 | # Intellij project files
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Gradle stuff
20 | gradlew
21 | gradlew.bat
22 | gradle/
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.1.1
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## master
2 |
3 | ## 1.2.10
4 | * Fix accessibility issues with TalkBack
5 |
6 | ## 1.2.7
7 | * Update okhttp to 3.10.0 (fixes "Expected ':status' header not present" error)
8 |
9 | ## 1.2.6
10 | * Update for SDK 26
11 |
12 | ## 1.2.5
13 |
14 | * Add af localization
15 | * Add proguard config
16 | * Fix context leak
17 | * Switch to okhttp
18 |
19 | ## 1.2.4
20 |
21 | * Always check that emails are valid
22 |
23 | ## 1.2.3
24 |
25 | * Add localizations
26 |
27 | ## 1.2.2
28 |
29 | * Fix a regression that broke article browsing
30 |
31 | ## 1.2.1
32 |
33 | * Fix Session to be a real singleton. Thanks to everyone who reported this. It was resulting in some random crashes.
34 | * Fill in missing translations.
35 | * Limit idea name to 140 chars, which is what the API will accept.
36 | * Delete saved access token and retry without it if it is invalid.
37 | * Fix reporting of instant answers metrics.
38 | * Store the config in local storage so that if we get evicted from memory, we still have a config.
39 |
40 | ## 1.2.0
41 |
42 | * Pull in appcompat-v7 to provide an ActionBar on older OS versions
43 | * Fix loading indicator dimensions on older OS versions
44 | * Unescape html entities in topic and forum names
45 | * Hide post idea item on forum view if post idea is disabled
46 | * Use a theme separate from the host app
47 | * Add Spanish translation
48 | * Bump compileSdkVersion to 21 (Android 5)
49 |
50 | ## 1.1.2
51 |
52 | * Add programmatic support for ticket attachments
53 | * Fix issue with stretched images in knowledgebase articles
54 |
55 | ## 1.1.1
56 |
57 | * Translation updates
58 | * Fix a bug where the sdk could not be used if an admin email address was passed to identifyUser()
59 | * Fix a few crash sources related to Activity state
60 | * Add support for displaying suggestion rank
61 |
62 | ## 1.1.0
63 |
64 | * Fix a bug that sent way too much traffic to the UserVoice api
65 |
66 | ## 1.0.1
67 |
68 | * Add portuguese translation
69 | * fix a bug where UserVoice.init() would show an error message to the user if the server was unreachable
70 | * Prevent duplicated ideas or tickets if the user taps submit multiple times
71 | * Add email validation
72 | * Force soft keyboard to hide when tapping Next in form views
73 |
74 | ## 1.0.0
75 |
76 | * First versioned release
77 |
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 UserVoice
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Resources/icons.sketch/Data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/Resources/icons.sketch/Data
--------------------------------------------------------------------------------
/Resources/icons.sketch/QuickLook/Preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/Resources/icons.sketch/QuickLook/Preview.png
--------------------------------------------------------------------------------
/Resources/icons.sketch/QuickLook/Thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/Resources/icons.sketch/QuickLook/Thumbnail.png
--------------------------------------------------------------------------------
/Resources/icons.sketch/fonts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/Resources/icons.sketch/fonts
--------------------------------------------------------------------------------
/Resources/icons.sketch/version:
--------------------------------------------------------------------------------
1 | 10
--------------------------------------------------------------------------------
/UVDemo/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/.name:
--------------------------------------------------------------------------------
1 | UVDemo
--------------------------------------------------------------------------------
/UVDemo/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/UVDemo/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/UVDemo/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/UVDemo/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/UVDemo/.idea/libraries/commons_codec_1_8.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/libraries/httpcore_4_0_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/libraries/signpost_commonshttp4_1_2_1_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/libraries/signpost_core_1_2_1_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/libraries/support_v4_19_0_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UVDemo/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | localhost
108 | 5050
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/UVDemo/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/UVDemo/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/UVDemo/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UVDemo/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/UVDemo/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | maven {
5 | url 'https://maven.google.com/'
6 | name 'Google'
7 | }
8 | mavenCentral()
9 | google()
10 | }
11 |
12 | dependencies {
13 | classpath 'com.android.tools.build:gradle:3.3.2'
14 | }
15 | }
16 |
17 | apply plugin: 'com.android.application'
18 |
19 | repositories {
20 | maven {
21 | url 'https://maven.google.com/'
22 | name 'Google'
23 | }
24 | mavenCentral()
25 | google()
26 | }
27 |
28 | android {
29 | compileSdkVersion 28
30 | sourceSets {
31 | main {
32 | manifest.srcFile 'AndroidManifest.xml'
33 | java.srcDirs = ['src']
34 | resources.srcDirs = ['src']
35 | res.srcDirs = ['res']
36 | }
37 | }
38 | signingConfigs {
39 | release {
40 | storeFile file("../demo.keystore")
41 | storePassword ""
42 | keyAlias "demo"
43 | keyPassword ""
44 | }
45 | }
46 | buildTypes {
47 | release {
48 | signingConfig signingConfigs.release
49 | minifyEnabled true
50 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
51 | }
52 | proguard {
53 | minifyEnabled true
54 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
55 | signingConfig signingConfigs.release
56 | }
57 | debug {
58 | debuggable true
59 | }
60 | }
61 | defaultConfig {
62 | minSdkVersion 18
63 | targetSdkVersion 26
64 | }
65 | }
66 |
67 | task askForPasswords << {
68 | // Must create String because System.readPassword() returns char[]
69 | // (and assigning that below fails silently)
70 | def storePw = new String(System.console().readPassword("Keystore password: "))
71 | def keyPw = new String(System.console().readPassword("Key password: "))
72 |
73 | android.signingConfigs.release.storePassword = storePw
74 | android.signingConfigs.release.keyPassword = keyPw
75 | }
76 |
77 | tasks.whenTaskAdded { theTask ->
78 | if (theTask.name.equals("packageRelease")) {
79 | theTask.dependsOn "askForPasswords"
80 | }
81 | }
82 |
83 | dependencies {
84 | implementation 'com.android.support:support-core-utils:28.0.0'
85 | implementation project(':UserVoiceSDK')
86 | }
87 |
--------------------------------------------------------------------------------
/UVDemo/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UVDemo/ic_launcher-web.png
--------------------------------------------------------------------------------
/UVDemo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in ~/android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep class android.support.v7.widget.SearchView { *; }
19 |
20 | -dontwarn java.nio.file.Files
21 | -dontwarn java.nio.file.Path
22 | -dontwarn java.nio.file.OpenOption
23 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
24 |
--------------------------------------------------------------------------------
/UVDemo/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-18
15 |
16 |
--------------------------------------------------------------------------------
/UVDemo/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UVDemo/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/UVDemo/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UVDemo/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/UVDemo/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UVDemo/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/UVDemo/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UVDemo/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/UVDemo/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
20 |
21 |
28 |
29 |
35 |
36 |
42 |
43 |
49 |
--------------------------------------------------------------------------------
/UVDemo/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/UVDemo/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/UVDemo/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
--------------------------------------------------------------------------------
/UVDemo/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/UVDemo/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/UVDemo/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/UVDemo/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | UVDemo
4 |
--------------------------------------------------------------------------------
/UVDemo/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/UVDemo/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':UserVoiceSDK'
--------------------------------------------------------------------------------
/UVDemo/src/com/uservoice/uvdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uvdemo;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.view.Menu;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.ViewConfiguration;
11 |
12 | import com.uservoice.uservoicesdk.Config;
13 | import com.uservoice.uservoicesdk.UserVoice;
14 |
15 | public class MainActivity extends Activity {
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.activity_main);
21 |
22 | Config config = new Config("demo.uservoice.com");
23 | UserVoice.init(config, this);
24 |
25 | // hack to always show the overflow menu in the action bar
26 | try {
27 | ViewConfiguration viewConfig = ViewConfiguration.get(this);
28 | Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
29 | if (menuKeyField != null) {
30 | menuKeyField.setAccessible(true);
31 | menuKeyField.setBoolean(viewConfig, false);
32 | }
33 | } catch (Exception ex) {
34 | // Ignore
35 | }
36 | }
37 |
38 | @Override
39 | public boolean onCreateOptionsMenu(Menu menu) {
40 | // Inflate the menu; this adds items to the action bar if it is present.
41 | getMenuInflater().inflate(R.menu.main, menu);
42 | return true;
43 | }
44 |
45 | public void launchFeedback(MenuItem menuItem) {
46 | UserVoice.launchUserVoice(this);
47 | }
48 |
49 | public void launchFeedback(View view) {
50 | UserVoice.launchUserVoice(this);
51 | }
52 |
53 | public void launchForum(View view) {
54 | UserVoice.launchForum(this);
55 | }
56 |
57 | public void launchContactUs(View view) {
58 | UserVoice.launchContactUs(this);
59 | }
60 |
61 | public void launchPostIdea(View view) {
62 | UserVoice.launchPostIdea(this);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/.name:
--------------------------------------------------------------------------------
1 | UserVoiceSDK
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/UserVoiceSDK/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UserVoiceSDK/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/UserVoiceSDK/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | maven {
5 | url 'https://maven.google.com/'
6 | name 'Google'
7 | }
8 | mavenCentral()
9 | google()
10 | }
11 |
12 | dependencies {
13 | classpath 'com.android.tools.build:gradle:3.3.2'
14 | }
15 | }
16 |
17 | apply plugin: 'com.android.library'
18 |
19 | apply plugin: 'maven'
20 |
21 | uploadArchives {
22 | repositories.mavenDeployer {
23 | pom.groupId = 'com.uservoice'
24 | pom.artifactId = 'uservoice-android-sdk'
25 | pom.version = '1.2.10'
26 | // Add other pom properties here if you want (developer details / licenses)
27 | repository(url: "file:///Users/austin/tmp/")
28 | }
29 | }
30 |
31 | task sourcesJar(type: Jar) {
32 | classifier = 'sources'
33 | from 'src', 'res'
34 | }
35 |
36 | artifacts {
37 | archives sourcesJar
38 | }
39 |
40 | repositories {
41 | mavenCentral()
42 | }
43 |
44 | android {
45 | compileSdkVersion 28
46 |
47 | sourceSets {
48 | main {
49 | manifest.srcFile 'AndroidManifest.xml'
50 | java.srcDirs = ['src']
51 | resources.srcDirs = ['src']
52 | res.srcDirs = ['res']
53 | }
54 | }
55 | defaultConfig {
56 | minSdkVersion 18
57 | targetSdkVersion 26
58 | }
59 | }
60 |
61 | dependencies {
62 | implementation 'com.android.support:support-core-utils:28.0.0'
63 | implementation 'com.android.support:appcompat-v7:28.0.0'
64 | implementation 'com.squareup.okhttp3:okhttp:3.11.0'
65 | implementation 'oauth.signpost:signpost-core:1.2.1.2'
66 | }
67 |
--------------------------------------------------------------------------------
/UserVoiceSDK/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/UserVoiceSDK/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 24 14:33:49 EDT 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.zip
7 |
--------------------------------------------------------------------------------
/UserVoiceSDK/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/UserVoiceSDK/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/ic_launcher-web.png
--------------------------------------------------------------------------------
/UserVoiceSDK/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/UserVoiceSDK/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-17
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_add.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_article.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_comment.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_contact.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_heart.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-hdpi/uv_idea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-hdpi/uv_idea.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_add.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_article.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_comment.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_contact.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_heart.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-ldpi/uv_idea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-ldpi/uv_idea.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_add.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_article.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_comment.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_contact.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_heart.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-mdpi/uv_idea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-mdpi/uv_idea.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_add.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_article.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_comment.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_contact.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_heart.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable-xhdpi/uv_idea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/UserVoiceSDK/res/drawable-xhdpi/uv_idea.png
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable/uv_admin_response.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable/uv_clickable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable/uv_list_separator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/drawable/uv_list_separator_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_article_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
17 |
21 |
22 |
28 |
29 |
33 |
34 |
42 |
43 |
48 |
49 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_comment_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
24 |
25 |
30 |
31 |
32 |
33 |
40 |
41 |
42 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_comment_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
28 |
29 |
36 |
37 |
44 |
45 |
46 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_contact_button_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_contact_text_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_header_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_header_item_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_header_item_light_no_padding.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_idea_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_idea_help_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_instant_answer_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
22 |
23 |
29 |
30 |
37 |
38 |
43 |
44 |
51 |
52 |
58 |
59 |
67 |
68 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_list_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_loading_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_password_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_powered_by_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_select_field_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
15 |
16 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_signin_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
30 |
31 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_subscribe_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_suggestion_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
24 |
25 |
30 |
31 |
36 |
37 |
45 |
46 |
54 |
55 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_text_field_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/layout/uv_text_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/menu/uv_forum.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/menu/uv_portal.xml:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 64dp
4 |
5 |
--------------------------------------------------------------------------------
/UserVoiceSDK/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
18 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/UserVoice.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk;
2 |
3 | import java.util.Map;
4 |
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | import com.uservoice.uservoicesdk.activity.ContactActivity;
9 | import com.uservoice.uservoicesdk.activity.ForumActivity;
10 | import com.uservoice.uservoicesdk.activity.PortalActivity;
11 | import com.uservoice.uservoicesdk.activity.PostIdeaActivity;
12 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
13 | import com.uservoice.uservoicesdk.model.ClientConfig;
14 | import com.uservoice.uservoicesdk.rest.RestResult;
15 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
16 |
17 | public class UserVoice {
18 |
19 | public static void launchUserVoice(Context context) {
20 | context.startActivity(new Intent(context, PortalActivity.class));
21 | }
22 |
23 | public static void launchForum(Context context) {
24 | context.startActivity(new Intent(context, ForumActivity.class));
25 | }
26 |
27 | public static void launchContactUs(Context context) {
28 | context.startActivity(new Intent(context, ContactActivity.class));
29 | }
30 |
31 | public static void launchPostIdea(Context context) {
32 | context.startActivity(new Intent(context, PostIdeaActivity.class));
33 | }
34 |
35 | public static void init(Config config, Context context) {
36 | Session.reset();
37 | Session.getInstance().init(context, config);
38 | Babayaga.init(context);
39 | }
40 |
41 | public static void setExternalId(String scope, String id) {
42 | Session.getInstance().setExternalId(scope, id);
43 | }
44 |
45 | public static void track(Context context, String event, Map properties) {
46 | Babayaga.track(context, event, properties);
47 | }
48 |
49 | public static void track(Context context, String event) {
50 | track(context, event, null);
51 | }
52 |
53 | public static String getVersion() {
54 | return "1.2.10";
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/ArticleActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Intent;
5 | import android.graphics.Color;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.view.View;
11 | import android.webkit.JavascriptInterface;
12 | import android.webkit.WebView;
13 | import android.webkit.WebViewClient;
14 | import android.widget.LinearLayout;
15 | import android.widget.ListView;
16 | import android.widget.Toast;
17 |
18 | import com.uservoice.uservoicesdk.R;
19 | import com.uservoice.uservoicesdk.Session;
20 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
21 | import com.uservoice.uservoicesdk.dialog.UnhelpfulDialogFragment;
22 | import com.uservoice.uservoicesdk.model.Article;
23 | import com.uservoice.uservoicesdk.ui.Utils;
24 |
25 | public class ArticleActivity extends SearchActivity {
26 |
27 | private WebView webView;
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 |
33 | setContentView(R.layout.uv_article_layout);
34 | final Article article = getIntent().getParcelableExtra("article");
35 | setTitle(article.getTitle());
36 | webView = (WebView) findViewById(R.id.uv_webview);
37 | final View helpfulSection = findViewById(R.id.uv_helpful_section);
38 | Utils.displayArticle(webView, article, this);
39 | findViewById(R.id.uv_container).setBackgroundColor(Utils.isDarkTheme(this) ? Color.BLACK : Color.WHITE);
40 | webView.setWebViewClient(new WebViewClient() {
41 | @Override
42 | public void onPageFinished(WebView view, String url) {
43 | super.onPageFinished(view, url);
44 | helpfulSection.setVisibility(View.VISIBLE);
45 | }
46 | });
47 | findViewById(R.id.uv_helpful_button).setOnClickListener(new View.OnClickListener() {
48 | @Override
49 | public void onClick(View v) {
50 | Babayaga.track(ArticleActivity.this, Babayaga.Event.VOTE_ARTICLE, article.getId());
51 | Toast.makeText(ArticleActivity.this, R.string.uv_thanks, Toast.LENGTH_SHORT).show();
52 | }
53 | });
54 | findViewById(R.id.uv_unhelpful_button).setOnClickListener(new View.OnClickListener() {
55 | @Override
56 | public void onClick(View v) {
57 | UnhelpfulDialogFragment dialog = new UnhelpfulDialogFragment();
58 | dialog.show(getSupportFragmentManager(), "UnhelpfulDialogFragment");
59 | }
60 | });
61 |
62 | Babayaga.track(this, Babayaga.Event.VIEW_ARTICLE, article.getId());
63 | }
64 |
65 | @Override
66 | public ListView getListView() {
67 | // This is called by setupScopedSearch(menu) to make sure the list is added,
68 | // but we don't want a list. Should probably refactor this somehow.
69 | return null;
70 | }
71 |
72 | @Override
73 | public boolean onPrepareOptionsMenu(Menu menu) {
74 | MenuItem item = menu.findItem(R.id.uv_action_contact);
75 | if (!Session.getInstance().getConfig(this).shouldShowContactUs()) {
76 | item.setVisible(false);
77 | }
78 | super.onPrepareOptionsMenu(menu);
79 | return true;
80 | }
81 |
82 | @Override
83 | @SuppressLint("NewApi")
84 | public boolean onCreateOptionsMenu(Menu menu) {
85 | getMenuInflater().inflate(R.menu.uv_portal, menu);
86 | setupScopedSearch(menu);
87 | return true;
88 | }
89 |
90 | @Override
91 | public boolean onOptionsItemSelected(MenuItem item) {
92 | if (item.getItemId() == R.id.uv_action_contact) {
93 | startActivity(new Intent(this, ContactActivity.class));
94 | return true;
95 | }
96 | return super.onOptionsItemSelected(item);
97 | }
98 |
99 | @Override
100 | public void finish() {
101 | // This is what you have to do to make it stop the flash player
102 | webView.loadData("", "text/html", "utf-8");
103 | super.finish();
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.support.v7.app.ActionBar;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.MenuItem;
8 |
9 | import com.uservoice.uservoicesdk.Session;
10 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
11 | import com.uservoice.uservoicesdk.ui.MixedSearchAdapter;
12 |
13 | public class BaseActivity extends AppCompatActivity {
14 |
15 | protected ActionBar.Tab allTab;
16 | protected ActionBar.Tab articlesTab;
17 | protected ActionBar.Tab ideasTab;
18 | protected MixedSearchAdapter searchAdapter;
19 | protected ActionBar actionBar;
20 |
21 | @Override
22 | @SuppressLint("NewApi")
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | if (hasActionBar()) {
26 | actionBar = getSupportActionBar();
27 | actionBar.setDisplayHomeAsUpEnabled(true);
28 | }
29 | }
30 |
31 | @Override
32 | public boolean onOptionsItemSelected(MenuItem item) {
33 | if (item.getItemId() == android.R.id.home) {
34 | onBackPressed();
35 | return true;
36 | }
37 | return super.onOptionsItemSelected(item);
38 | }
39 |
40 | @SuppressLint("NewApi")
41 | public boolean hasActionBar() {
42 | return getSupportActionBar() != null;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/ContactActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.uservoice.uservoicesdk.R;
6 | import com.uservoice.uservoicesdk.ui.ContactAdapter;
7 | import com.uservoice.uservoicesdk.ui.InstantAnswersAdapter;
8 |
9 | public class ContactActivity extends InstantAnswersActivity {
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setTitle(R.string.uv_contact_us);
15 | }
16 |
17 | @Override
18 | protected InstantAnswersAdapter createAdapter() {
19 | return new ContactAdapter(this);
20 | }
21 |
22 | @Override
23 | protected int getDiscardDialogMessage() {
24 | return R.string.uv_msg_confirm_discard_message;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/InstantAnswersActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.os.Bundle;
6 | import android.view.ViewGroup;
7 | import android.view.WindowManager;
8 |
9 | import com.uservoice.uservoicesdk.R;
10 | import com.uservoice.uservoicesdk.compatibility.FragmentListActivity;
11 | import com.uservoice.uservoicesdk.flow.InitManager;
12 | import com.uservoice.uservoicesdk.ui.InstantAnswersAdapter;
13 |
14 | public abstract class InstantAnswersActivity extends FragmentListActivity {
15 |
16 | private InstantAnswersAdapter adapter;
17 |
18 | public InstantAnswersActivity() {
19 | super();
20 | }
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 |
26 | getListView().setDivider(null);
27 |
28 | adapter = createAdapter();
29 | setListAdapter(adapter);
30 | getListView().setOnHierarchyChangeListener(adapter);
31 | getListView().setOnItemClickListener(adapter);
32 | getListView().setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
33 | new InitManager(this, new Runnable() {
34 | @Override
35 | public void run() {
36 | onInitialize();
37 | }
38 | }).init();
39 | getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
40 | }
41 |
42 | protected void onInitialize() {
43 | adapter.notifyDataSetChanged();
44 | }
45 |
46 | @Override
47 | public void onBackPressed() {
48 | if (adapter.hasText()) {
49 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
50 | builder.setTitle(R.string.uv_confirm);
51 | builder.setMessage(getDiscardDialogMessage());
52 | builder.setPositiveButton(R.string.uv_yes, new DialogInterface.OnClickListener() {
53 | @Override
54 | public void onClick(DialogInterface dialog, int which) {
55 | finish();
56 | }
57 | });
58 | builder.setNegativeButton(R.string.uv_no, null);
59 | builder.show();
60 | } else {
61 | super.onBackPressed();
62 | }
63 | }
64 |
65 | protected abstract InstantAnswersAdapter createAdapter();
66 |
67 | protected abstract int getDiscardDialogMessage();
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/PortalActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.Menu;
6 | import android.view.MenuItem;
7 |
8 | import com.uservoice.uservoicesdk.R;
9 | import com.uservoice.uservoicesdk.Session;
10 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
11 | import com.uservoice.uservoicesdk.ui.PortalAdapter;
12 |
13 | public class PortalActivity extends SearchActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 |
19 | setTitle(R.string.uv_portal_title);
20 | getListView().setDivider(null);
21 | setListAdapter(new PortalAdapter(this));
22 | getListView().setOnItemClickListener(getModelAdapter());
23 |
24 | Babayaga.track(this, Babayaga.Event.VIEW_KB);
25 | }
26 |
27 | @Override
28 | public boolean onPrepareOptionsMenu(Menu menu) {
29 | MenuItem item = menu.findItem(R.id.uv_action_contact);
30 | if (!Session.getInstance().getConfig(this).shouldShowContactUs()) {
31 | item.setVisible(false);
32 | }
33 | super.onPrepareOptionsMenu(menu);
34 | return true;
35 | }
36 |
37 | @Override
38 | public boolean onCreateOptionsMenu(Menu menu) {
39 | getMenuInflater().inflate(R.menu.uv_portal, menu);
40 | setupScopedSearch(menu);
41 | return true;
42 | }
43 |
44 | @Override
45 | public boolean onOptionsItemSelected(MenuItem item) {
46 | if (item.getItemId() == R.id.uv_action_contact) {
47 | startActivity(new Intent(this, ContactActivity.class));
48 | return true;
49 | }
50 | return super.onOptionsItemSelected(item);
51 | }
52 |
53 | public PortalAdapter getModelAdapter() {
54 | return (PortalAdapter) getListAdapter();
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/PostIdeaActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.uservoice.uservoicesdk.R;
6 | import com.uservoice.uservoicesdk.Session;
7 | import com.uservoice.uservoicesdk.model.Forum;
8 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
9 | import com.uservoice.uservoicesdk.ui.InstantAnswersAdapter;
10 | import com.uservoice.uservoicesdk.ui.PostIdeaAdapter;
11 |
12 | public class PostIdeaActivity extends InstantAnswersActivity {
13 |
14 | @Override
15 | protected void onInitialize() {
16 | if (Session.getInstance().getForum() != null) {
17 | super.onInitialize();
18 | } else {
19 | Forum.loadForum(this, Session.getInstance().getConfig(this).getForumId(), new DefaultCallback(this) {
20 | @Override
21 | public void onModel(Forum model) {
22 | Session.getInstance().setForum(model);
23 | PostIdeaActivity.super.onInitialize();
24 | }
25 | });
26 |
27 | }
28 | }
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setTitle(R.string.uv_idea_form_title);
34 | }
35 |
36 | @Override
37 | protected InstantAnswersAdapter createAdapter() {
38 | return new PostIdeaAdapter(this);
39 | }
40 |
41 | @Override
42 | protected int getDiscardDialogMessage() {
43 | return R.string.uv_msg_confirm_discard_idea;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/activity/SearchActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.activity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.support.v4.app.FragmentTransaction;
5 | import android.support.v4.view.MenuItemCompat;
6 | import android.support.v7.app.ActionBar;
7 | import android.support.v7.widget.SearchView;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.widget.ListView;
11 | import android.widget.ViewFlipper;
12 |
13 | import com.uservoice.uservoicesdk.R;
14 | import com.uservoice.uservoicesdk.compatibility.FragmentListActivity;
15 | import com.uservoice.uservoicesdk.ui.MixedSearchAdapter;
16 | import com.uservoice.uservoicesdk.ui.PortalAdapter;
17 | import com.uservoice.uservoicesdk.ui.SearchAdapter;
18 | import com.uservoice.uservoicesdk.ui.SearchExpandListener;
19 | import com.uservoice.uservoicesdk.ui.SearchQueryListener;
20 |
21 | @SuppressLint("NewApi")
22 | public abstract class SearchActivity extends FragmentListActivity {
23 | private int originalNavigationMode = -1;
24 |
25 | public SearchAdapter> getSearchAdapter() {
26 | return searchAdapter;
27 | }
28 |
29 | public void updateScopedSearch(int results, int articleResults, int ideaResults) {
30 | if (hasActionBar()) {
31 | allTab.setText(String.format("%s (%d)", getString(R.string.uv_all_results_filter), results));
32 | articlesTab.setText(String.format("%s (%d)", getString(R.string.uv_articles_filter), articleResults));
33 | ideasTab.setText(String.format("%s (%d)", getString(R.string.uv_ideas_filter), ideaResults));
34 | }
35 | }
36 |
37 | public void showSearch() {
38 | ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.uv_view_flipper);
39 | viewFlipper.setDisplayedChild(1);
40 | if (hasActionBar()) {
41 | if (originalNavigationMode == -1)
42 | originalNavigationMode = actionBar.getNavigationMode();
43 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
44 | }
45 | }
46 |
47 | public void hideSearch() {
48 | ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.uv_view_flipper);
49 | viewFlipper.setDisplayedChild(0);
50 | if (hasActionBar()) {
51 | actionBar.setNavigationMode(originalNavigationMode == -1 ? ActionBar.NAVIGATION_MODE_STANDARD : originalNavigationMode);
52 | }
53 | }
54 |
55 | protected void setupScopedSearch(Menu menu) {
56 | MenuItem searchItem = menu.findItem(R.id.uv_action_search);
57 | if (hasActionBar()) {
58 | MenuItemCompat.setOnActionExpandListener(searchItem, new SearchExpandListener(this));
59 | SearchView search = (SearchView) MenuItemCompat.getActionView(searchItem);
60 | search.setOnQueryTextListener(new SearchQueryListener(this));
61 | searchAdapter = new MixedSearchAdapter(this);
62 | ListView searchView = new ListView(this);
63 | searchView.setAdapter(searchAdapter);
64 | searchView.setOnItemClickListener(searchAdapter);
65 |
66 | // ensure that the viewflipper is set up
67 | getListView();
68 |
69 | ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.uv_view_flipper);
70 | viewFlipper.addView(searchView, 1);
71 |
72 | ActionBar.TabListener listener = new ActionBar.TabListener() {
73 | @Override
74 | public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
75 | }
76 |
77 | @Override
78 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
79 | searchAdapter.setScope((Integer) tab.getTag());
80 | }
81 |
82 | @Override
83 | public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
84 | }
85 | };
86 | allTab = actionBar.newTab().setText(getString(R.string.uv_all_results_filter)).setTabListener(listener).setTag(PortalAdapter.SCOPE_ALL);
87 | actionBar.addTab(allTab);
88 | articlesTab = actionBar.newTab().setText(getString(R.string.uv_articles_filter)).setTabListener(listener).setTag(PortalAdapter.SCOPE_ARTICLES);
89 | actionBar.addTab(articlesTab);
90 | ideasTab = actionBar.newTab().setText(getString(R.string.uv_ideas_filter)).setTabListener(listener).setTag(PortalAdapter.SCOPE_IDEAS);
91 | actionBar.addTab(ideasTab);
92 | } else {
93 | searchItem.setVisible(false);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/babayaga/Babayaga.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.babayaga;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import android.content.Context;
9 | import android.content.SharedPreferences;
10 | import android.content.SharedPreferences.Editor;
11 |
12 | import com.uservoice.uservoicesdk.Session;
13 | import com.uservoice.uservoicesdk.model.BaseModel;
14 |
15 | public class Babayaga {
16 |
17 | static String DOMAIN = "by.uservoice.com";
18 | public static String CHANNEL = "d";
19 | public static String EXTERNAL_CHANNEL = "x";
20 |
21 | private static class Track {
22 | public String event;
23 | public Map eventProps;
24 |
25 | public Track(String event, Map eventProps) {
26 | this.event = event;
27 | this.eventProps = eventProps;
28 | }
29 | }
30 |
31 | private static String uvts;
32 | private static SharedPreferences prefs;
33 |
34 | public enum Event {
35 | VIEW_APP("g"),
36 | VIEW_FORUM("m"),
37 | VIEW_TOPIC("c"),
38 | VIEW_KB("k"),
39 | VIEW_CHANNEL("o"),
40 | VIEW_IDEA("i"),
41 | VIEW_ARTICLE("f"),
42 | AUTHENTICATE("u"),
43 | SEARCH_IDEAS("s"),
44 | SEARCH_ARTICLES("r"),
45 | VOTE_IDEA("v"),
46 | VOTE_ARTICLE("z"),
47 | SUBMIT_TICKET("t"),
48 | SUBMIT_IDEA("d"),
49 | SUBSCRIBE_IDEA("b"),
50 | IDENTIFY("y"),
51 | COMMENT_IDEA("h");
52 |
53 | private final String code;
54 |
55 | private Event(String code) {
56 | this.code = code;
57 | }
58 |
59 | public String getCode() {
60 | return code;
61 | }
62 | }
63 |
64 | public static void setUvts(String uvts) {
65 | Babayaga.uvts = uvts;
66 | Editor edit = prefs.edit();
67 | edit.putString("uvts", uvts);
68 | edit.commit();
69 | }
70 |
71 | public static void track(Context context, Event event) {
72 | track(context, event, null);
73 | }
74 |
75 | public static void track(Context context, Event event, String searchText, List extends BaseModel> results) {
76 | Map props = new HashMap();
77 | List ids = new ArrayList(results.size());
78 | for (BaseModel model : results) {
79 | ids.add(model.getId());
80 | }
81 | props.put("ids", ids);
82 | props.put("text", searchText);
83 | track(context, event, props);
84 | }
85 |
86 | public static void track(Context context, Event event, int id) {
87 | Map props = new HashMap();
88 | props.put("id", id);
89 | track(context, event, props);
90 | }
91 |
92 | public static void track(Context context, Event event, Map eventProps) {
93 | track(context, event.getCode(), eventProps);
94 | }
95 |
96 | public static void track(Context context, String event, Map eventProps) {
97 | // Log.d("UV", "BY flushing: " + event);
98 | new BabayagaTask(context, event, uvts, eventProps).execute();
99 | }
100 |
101 | public static void init(Context context) {
102 | prefs = context.getSharedPreferences("uv", 0);
103 | if (prefs.contains("uvts")) {
104 | uvts = prefs.getString("uvts", null);
105 | }
106 | track(context, Event.VIEW_APP);
107 | }
108 |
109 | public static String getUvts() {
110 | return uvts;
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/babayaga/BabayagaTask.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.babayaga;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.net.URLEncoder;
5 | import java.util.Date;
6 | import java.util.Map;
7 |
8 | import org.json.JSONObject;
9 |
10 | import android.content.Context;
11 | import android.os.AsyncTask;
12 | import android.util.Base64;
13 | import android.util.Log;
14 |
15 | import okhttp3.OkHttpClient;
16 | import okhttp3.Request;
17 | import okhttp3.Response;
18 | import com.uservoice.uservoicesdk.Session;
19 | import com.uservoice.uservoicesdk.UserVoice;
20 |
21 | public class BabayagaTask extends AsyncTask {
22 |
23 | private final String event;
24 | private final Map eventProps;
25 | private final String uvts;
26 | private final Context context;
27 |
28 | public BabayagaTask(Context context, String event, String uvts, Map eventProps) {
29 | this.event = event;
30 | this.uvts = uvts;
31 | this.eventProps = eventProps;
32 | this.context = context.getApplicationContext();
33 | }
34 |
35 | @Override
36 | protected Void doInBackground(String... args) {
37 | try {
38 | JSONObject data = new JSONObject();
39 | Map traits = Session.getInstance().getConfig(context).getUserTraits();
40 | if (traits != null && !traits.isEmpty()) {
41 | data.put("u", new JSONObject(traits));
42 | }
43 | if (eventProps != null && !eventProps.isEmpty()) {
44 | data.put("e", eventProps);
45 | }
46 | String subdomain;
47 | String route;
48 | if (Session.getInstance().getClientConfig() != null) {
49 | subdomain = Session.getInstance().getClientConfig().getSubdomainId();
50 | route = "t";
51 | } else {
52 | subdomain = Session.getInstance().getConfig(context).getSite().split("\\.")[0];
53 | route = "t/k";
54 | }
55 | String channel = event.equals(Babayaga.Event.VIEW_APP.toString()) ? Babayaga.EXTERNAL_CHANNEL : Babayaga.CHANNEL;
56 | StringBuilder url = new StringBuilder(String.format("https://%s/%s/%s/%s/%s", Babayaga.DOMAIN, route, subdomain, channel, event));
57 | if (uvts != null) {
58 | url.append("/");
59 | url.append(uvts);
60 | }
61 | url.append("/track.js?_=");
62 | url.append(new Date().getTime());
63 | url.append("&c=_");
64 | if (data.length() != 0) {
65 | url.append("&d=");
66 | try {
67 | url.append(URLEncoder.encode(Base64.encodeToString(data.toString().getBytes(), Base64.NO_WRAP), "UTF-8"));
68 | } catch (UnsupportedEncodingException e) {
69 | throw new RuntimeException(e);
70 | }
71 | }
72 | Log.d("UV", url.toString());
73 |
74 | OkHttpClient client = new OkHttpClient();
75 | Request request = new Request.Builder()
76 | .get()
77 | .url(url.toString())
78 | .addHeader("User-Agent", String.format("uservoice-android-%s", UserVoice.getVersion()))
79 | .build();
80 |
81 | Response response = client.newCall(request).execute();
82 | int statusCode = response.code();
83 | if (statusCode != 200)
84 | return null;
85 | String body = response.body().string();
86 | if (body.length() > 0) {
87 | String payload = body.substring(2, body.length() - 2);
88 | JSONObject responseData = new JSONObject(payload);
89 | String uvts = responseData.getString("uvts");
90 | Babayaga.setUvts(uvts);
91 | }
92 | } catch (Exception e) {
93 | e.printStackTrace();
94 | Log.e("UV", String.format("%s: %s", e.getClass().getName(), e.getMessage()));
95 | }
96 | return null;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/compatibility/FragmentListActivity.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.compatibility;
2 |
3 | import android.app.Activity;
4 | import android.app.ListActivity;
5 | import android.os.Bundle;
6 | import android.os.Handler;
7 | import android.support.v4.app.Fragment;
8 | import android.view.View;
9 | import android.widget.AdapterView;
10 | import android.widget.ListAdapter;
11 | import android.widget.ListView;
12 | import android.widget.ViewFlipper;
13 |
14 | import com.uservoice.uservoicesdk.R;
15 | import com.uservoice.uservoicesdk.activity.BaseActivity;
16 |
17 | /**
18 | * Copy from Android source to enable {@link Fragment} support.
19 | *
20 | * @see ListActivity
21 | */
22 | public abstract class FragmentListActivity extends BaseActivity {
23 |
24 | // changed to private as original suggested
25 | private ListAdapter mAdapter;
26 | // changed to private as original suggested
27 | private ListView mList;
28 |
29 | private Handler mHandler = new Handler();
30 | private boolean mFinishedStart = false;
31 |
32 | private Runnable mRequestFocus = new Runnable() {
33 | public void run() {
34 | mList.focusableViewAvailable(mList);
35 | }
36 | };
37 |
38 | /**
39 | * This method will be called when an item in the list is selected.
40 | * Subclasses should override. Subclasses can call
41 | * getListView().getItemAtPosition(position) if they need to access the data
42 | * associated with the selected item.
43 | *
44 | * @param l The ListView where the click happened
45 | * @param v The view that was clicked within the ListView
46 | * @param position The position of the view in the list
47 | * @param id The row id of the item that was clicked
48 | */
49 | protected void onListItemClick(ListView l, View v, int position, long id) {
50 | }
51 |
52 | /**
53 | * Ensures the list view has been created before Activity restores all of
54 | * the view states.
55 | *
56 | * @see Activity#onRestoreInstanceState(Bundle)
57 | */
58 | @Override
59 | protected void onRestoreInstanceState(Bundle state) {
60 | ensureList();
61 | super.onRestoreInstanceState(state);
62 | }
63 |
64 |
65 | /**
66 | * Provide the cursor for the list view.
67 | */
68 | public void setListAdapter(ListAdapter adapter) {
69 | synchronized (this) {
70 | ensureList();
71 | mAdapter = adapter;
72 | mList.setAdapter(adapter);
73 | }
74 | }
75 |
76 | /**
77 | * Set the currently selected list item to the specified position with the
78 | * adapter's data
79 | *
80 | * @param position
81 | */
82 | public void setSelection(int position) {
83 | mList.setSelection(position);
84 | }
85 |
86 | /**
87 | * Get the position of the currently selected list item.
88 | */
89 | public int getSelectedItemPosition() {
90 | return mList.getSelectedItemPosition();
91 | }
92 |
93 | /**
94 | * Get the cursor row ID of the currently selected list item.
95 | */
96 | public long getSelectedItemId() {
97 | return mList.getSelectedItemId();
98 | }
99 |
100 | /**
101 | * Get the activity's list view widget.
102 | */
103 | public ListView getListView() {
104 | ensureList();
105 | return mList;
106 | }
107 |
108 | /**
109 | * Get the ListAdapter associated with this activity's ListView.
110 | */
111 | public ListAdapter getListAdapter() {
112 | return mAdapter;
113 | }
114 |
115 | private synchronized void ensureList() {
116 | if (mList != null)
117 | return;
118 |
119 | mList = new ListView(this);
120 | mList.setId(android.R.id.list);
121 | ViewFlipper viewFlipper = new ViewFlipper(this);
122 | viewFlipper.setId(R.id.uv_view_flipper);
123 | viewFlipper.addView(mList);
124 | setContentView(viewFlipper);
125 | mList.setOnItemClickListener(mOnClickListener);
126 | if (mFinishedStart) {
127 | setListAdapter(mAdapter);
128 | }
129 | mHandler.post(mRequestFocus);
130 | mFinishedStart = true;
131 | }
132 |
133 | private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
134 | public void onItemClick(AdapterView> parent, View v, int position, long id) {
135 | onListItemClick((ListView) parent, v, position, id);
136 | }
137 | };
138 | }
139 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/deflection/Deflection.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.deflection;
2 |
3 | import java.util.Date;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import android.content.Context;
12 | import android.util.Log;
13 |
14 | import com.uservoice.uservoicesdk.Session;
15 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
16 | import com.uservoice.uservoicesdk.model.Article;
17 | import com.uservoice.uservoicesdk.model.BaseModel;
18 | import com.uservoice.uservoicesdk.model.Suggestion;
19 | import com.uservoice.uservoicesdk.rest.RestMethod;
20 | import com.uservoice.uservoicesdk.rest.RestResult;
21 | import com.uservoice.uservoicesdk.rest.RestTask;
22 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
23 |
24 | public class Deflection {
25 |
26 | private static int interactionIdentifier = Integer.parseInt(String.valueOf(new Date().getTime()).substring(4));
27 | private static String searchText;
28 |
29 | public static void trackDeflection(Context context, String kind, String deflectingType, BaseModel deflector) {
30 | Map params = deflectionParams();
31 | params.put("kind", kind);
32 | params.put("deflecting_type", deflectingType);
33 | params.put("deflector_id", String.valueOf(deflector.getId()));
34 | params.put("deflector_type", (deflector instanceof Article) ? "Faq" : "Suggestion");
35 | new RestTask(context, RestMethod.GET, "/clients/widgets/omnibox/deflections/upsert.json", params, getCallback()).execute();
36 | }
37 |
38 | public static void trackSearchDeflection(Context context, List results, String deflectingType) {
39 | Map params = deflectionParams();
40 | params.put("kind", "list");
41 | params.put("deflecting_type", deflectingType);
42 | int articleResults = 0;
43 | int suggestionResults = 0;
44 | int index = 0;
45 | for (BaseModel model : results) {
46 | String prefix = "results[" + String.valueOf(index) + "]";
47 | params.put(prefix + "[position]", String.valueOf(index++));
48 | params.put(prefix + "[deflector_id]", String.valueOf(model.getId()));
49 | if (model instanceof Suggestion) {
50 | suggestionResults++;
51 | Suggestion suggestion = (Suggestion) model;
52 | params.put(prefix + "[weight]", String.valueOf(suggestion.getWeight()));
53 | params.put(prefix + "[deflector_type]", "Suggestion");
54 | } else if (model instanceof Article) {
55 | articleResults++;
56 | Article article = (Article) model;
57 | params.put(prefix + "[weight]", String.valueOf(article.getWeight()));
58 | params.put(prefix + "[deflector_type]", "Faq");
59 | }
60 | }
61 | params.put("faq_results", String.valueOf(articleResults));
62 | params.put("suggestion_results", String.valueOf(suggestionResults));
63 | new RestTask(context, RestMethod.GET, "/clients/widgets/omnibox/deflections/list_view.json", params, getCallback()).execute();
64 | }
65 |
66 | public static void setSearchText(String query) {
67 | if (query.equals(searchText))
68 | return;
69 | searchText = query;
70 | interactionIdentifier += 1;
71 | }
72 |
73 | private static RestTaskCallback getCallback() {
74 | return new RestTaskCallback(null) {
75 | @Override
76 | public void onError(RestResult result) {
77 | Log.e("UV", "Failed sending deflection: " + result.getMessage());
78 | }
79 |
80 | @Override
81 | public void onComplete(JSONObject result) throws JSONException {
82 | Log.d("UV", result.toString());
83 | }
84 | };
85 | }
86 |
87 | private static Map deflectionParams() {
88 | Map params = new HashMap();
89 | if (Babayaga.getUvts() != null) {
90 | params.put("uvts", Babayaga.getUvts());
91 | }
92 | params.put("channel", "android");
93 | params.put("search_term", searchText);
94 | params.put("interaction_identifier", String.valueOf(interactionIdentifier));
95 | params.put("subdomain_id", String.valueOf(Session.getInstance().getClientConfig().getSubdomainId()));
96 | return params;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/ArticleDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.webkit.WebView;
9 |
10 | import com.uservoice.uservoicesdk.R;
11 | import com.uservoice.uservoicesdk.activity.InstantAnswersActivity;
12 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
13 | import com.uservoice.uservoicesdk.deflection.Deflection;
14 | import com.uservoice.uservoicesdk.model.Article;
15 | import com.uservoice.uservoicesdk.ui.InstantAnswersAdapter;
16 | import com.uservoice.uservoicesdk.ui.Utils;
17 |
18 | @SuppressLint({"ValidFragment", "NewApi"})
19 | public class ArticleDialogFragment extends DialogFragmentBugfixed {
20 |
21 | private final Article article;
22 | private WebView webView;
23 | private String deflectingType;
24 |
25 | public ArticleDialogFragment(Article article, String deflectingType) {
26 | this.article = article;
27 | this.deflectingType = deflectingType;
28 | }
29 |
30 | @Override
31 | public Dialog onCreateDialog(Bundle savedInstanceState) {
32 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
33 | builder.setTitle(R.string.uv_article_instant_answer_question);
34 |
35 | webView = new WebView(getActivity());
36 | if (!Utils.isDarkTheme(getActivity())) {
37 | builder.setInverseBackgroundForced(true);
38 | }
39 | builder.setView(webView);
40 | Utils.displayArticle(webView, article, getActivity());
41 |
42 | builder.setNegativeButton(R.string.uv_no, new DialogInterface.OnClickListener() {
43 | @Override
44 | public void onClick(DialogInterface dialog, int which) {
45 | if (getActivity() instanceof InstantAnswersActivity) {
46 | Deflection.trackDeflection(getActivity(), "unhelpful", deflectingType, article);
47 | InstantAnswersActivity activity = (InstantAnswersActivity) getActivity();
48 | InstantAnswersAdapter adapter = (InstantAnswersAdapter) activity.getListAdapter();
49 | adapter.notHelpful();
50 | } else {
51 | UnhelpfulDialogFragment dialogFragment = new UnhelpfulDialogFragment();
52 | dialogFragment.show(getActivity().getSupportFragmentManager(), "UnhelpfulDialogFragment");
53 | }
54 | }
55 | });
56 |
57 | builder.setPositiveButton(R.string.uv_very_yes, new DialogInterface.OnClickListener() {
58 | @Override
59 | public void onClick(DialogInterface dialog, int which) {
60 | Babayaga.track(getActivity(), Babayaga.Event.VOTE_ARTICLE, article.getId());
61 | if (getActivity() instanceof InstantAnswersActivity) {
62 | Deflection.trackDeflection(getActivity(), "helpful", deflectingType, article);
63 | HelpfulDialogFragment helpfulDialog = new HelpfulDialogFragment();
64 | helpfulDialog.show(getActivity().getSupportFragmentManager(), "HelpfulDialogFragment");
65 | }
66 | }
67 | });
68 |
69 | Babayaga.track(getActivity(), Babayaga.Event.VIEW_ARTICLE, article.getId());
70 | return builder.create();
71 | }
72 |
73 | @Override
74 | public void onDismiss(DialogInterface dialog) {
75 | webView.onPause();
76 | webView.loadUrl("about:blank");
77 | super.onDismiss(dialog);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/CommentDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.view.WindowManager;
11 | import android.widget.EditText;
12 | import android.widget.TextView;
13 | import android.widget.Toast;
14 |
15 | import com.uservoice.uservoicesdk.R;
16 | import com.uservoice.uservoicesdk.Session;
17 | import com.uservoice.uservoicesdk.flow.SigninCallback;
18 | import com.uservoice.uservoicesdk.flow.SigninManager;
19 | import com.uservoice.uservoicesdk.model.Comment;
20 | import com.uservoice.uservoicesdk.model.Suggestion;
21 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
22 | import com.uservoice.uservoicesdk.ui.Utils;
23 |
24 | @SuppressLint("ValidFragment")
25 | public class CommentDialogFragment extends DialogFragmentBugfixed {
26 |
27 | private final Suggestion suggestion;
28 | private final SuggestionDialogFragment suggestionDialog;
29 |
30 | public CommentDialogFragment(Suggestion suggestion, SuggestionDialogFragment suggestionDialog) {
31 | this.suggestion = suggestion;
32 | this.suggestionDialog = suggestionDialog;
33 | }
34 |
35 | @Override
36 | public Dialog onCreateDialog(Bundle savedInstanceState) {
37 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
38 | if (!Utils.isDarkTheme(getActivity())) {
39 | builder.setInverseBackgroundForced(true);
40 | }
41 | builder.setTitle(R.string.uv_post_a_comment);
42 |
43 | View view = getActivity().getLayoutInflater().inflate(R.layout.uv_comment_dialog, null);
44 | final EditText textField = (EditText) view.findViewById(R.id.uv_comment_edit_text);
45 |
46 | View email = view.findViewById(R.id.uv_email);
47 | View name = view.findViewById(R.id.uv_name);
48 | final EditText emailField = (EditText) email.findViewById(R.id.uv_text_field);
49 | final EditText nameField = (EditText) name.findViewById(R.id.uv_text_field);
50 | if (Session.getInstance().getUser() != null) {
51 | email.setVisibility(View.GONE);
52 | name.setVisibility(View.GONE);
53 | } else {
54 | emailField.setText(Session.getInstance().getEmail(getActivity()));
55 | ((TextView) email.findViewById(R.id.uv_header_text)).setText(R.string.uv_your_email_address);
56 | nameField.setText(Session.getInstance().getName(getActivity()));
57 | ((TextView) name.findViewById(R.id.uv_header_text)).setText(R.string.uv_your_name);
58 | }
59 |
60 | builder.setView(view);
61 |
62 | builder.setNegativeButton(R.string.uv_cancel, null);
63 |
64 | final Activity context = getActivity();
65 | builder.setPositiveButton(R.string.uv_post_comment, new DialogInterface.OnClickListener() {
66 | @Override
67 | public void onClick(DialogInterface dialog, int which) {
68 | final String text = textField.getText().toString();
69 | if (text.trim().length() > 0) {
70 | SigninManager.signIn(getActivity(), emailField.getText().toString(), nameField.getText().toString(), new SigninCallback() {
71 | @Override
72 | public void onSuccess() {
73 | Comment.createComment(context, suggestion, text, new DefaultCallback(getActivity()) {
74 | @Override
75 | public void onModel(Comment model) {
76 | Toast.makeText(context, R.string.uv_msg_comment_posted, Toast.LENGTH_SHORT).show();
77 | suggestionDialog.commentPosted(model);
78 | }
79 | });
80 | }
81 | });
82 | }
83 | }
84 | });
85 | AlertDialog dialog = builder.create();
86 | dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
87 | return dialog;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/DialogFragmentBugfixed.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.DialogFragment;
5 |
6 | public abstract class DialogFragmentBugfixed extends DialogFragment {
7 | @Override
8 | public void onActivityCreated(Bundle savedInstanceState) {
9 | super.onActivityCreated(savedInstanceState);
10 | setRetainInstance(true);
11 | }
12 |
13 | @Override
14 | public void onDestroyView() {
15 | if (getDialog() != null && getRetainInstance())
16 | getDialog().setOnDismissListener(null);
17 |
18 | super.onDestroyView();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/HelpfulDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 |
8 | import com.uservoice.uservoicesdk.R;
9 | import com.uservoice.uservoicesdk.ui.Utils;
10 |
11 | public class HelpfulDialogFragment extends DialogFragmentBugfixed {
12 |
13 | @Override
14 | public Dialog onCreateDialog(Bundle savedInstanceState) {
15 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
16 |
17 | if (!Utils.isDarkTheme(getActivity())) {
18 | builder.setInverseBackgroundForced(true);
19 | }
20 | builder.setTitle(R.string.uv_helpful_article_message_question);
21 |
22 | builder.setNegativeButton(R.string.uv_no, new DialogInterface.OnClickListener() {
23 | @Override
24 | public void onClick(DialogInterface dialog, int which) {
25 | dialog.cancel();
26 | getActivity().finish();
27 | }
28 | });
29 |
30 | builder.setPositiveButton(R.string.uv_yes, null);
31 |
32 | return builder.create();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/PasswordDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 | import android.widget.EditText;
11 |
12 | import com.uservoice.uservoicesdk.R;
13 | import com.uservoice.uservoicesdk.Session;
14 | import com.uservoice.uservoicesdk.flow.SigninCallback;
15 | import com.uservoice.uservoicesdk.model.AccessToken;
16 | import com.uservoice.uservoicesdk.model.RequestToken;
17 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
18 | import com.uservoice.uservoicesdk.ui.Utils;
19 |
20 | @SuppressLint("ValidFragment")
21 | public class PasswordDialogFragment extends DialogFragmentBugfixed {
22 |
23 | private final SigninCallback callback;
24 | private EditText password;
25 |
26 | public PasswordDialogFragment(SigninCallback callback) {
27 | this.callback = callback;
28 | }
29 |
30 | @Override
31 | public Dialog onCreateDialog(Bundle savedInstanceState) {
32 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
33 | builder.setTitle(R.string.uv_password_dialog_title);
34 | if (!Utils.isDarkTheme(getActivity())) {
35 | builder.setInverseBackgroundForced(true);
36 | }
37 | View view = getActivity().getLayoutInflater().inflate(R.layout.uv_password_dialog, null);
38 | password = (EditText) view.findViewById(R.id.uv_password);
39 | builder.setView(view);
40 | builder.setNegativeButton(R.string.uv_cancel, null);
41 | builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
42 | @Override
43 | public void onClick(final DialogInterface dialog, int which) {
44 | if (Session.getInstance().getRequestToken() != null) {
45 | authorize();
46 | } else {
47 | RequestToken.getRequestToken(getActivity(), new DefaultCallback(getActivity()) {
48 | @Override
49 | public void onModel(RequestToken model) {
50 | Session.getInstance().setRequestToken(model);
51 | authorize();
52 | }
53 | });
54 | }
55 | }
56 | });
57 | AlertDialog dialog = builder.create();
58 | dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
59 | return dialog;
60 | }
61 |
62 | private void authorize() {
63 | AccessToken.authorize(getActivity(), Session.getInstance().getEmail(getActivity()), password.getText().toString(), new DefaultCallback(getActivity()) {
64 | @Override
65 | public void onModel(AccessToken model) {
66 | Session.getInstance().setAccessToken(model);
67 | callback.onSuccess();
68 | }
69 | });
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/SubscribeDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.view.WindowManager;
10 | import android.widget.Button;
11 | import android.widget.EditText;
12 | import android.widget.Toast;
13 |
14 | import com.uservoice.uservoicesdk.R;
15 | import com.uservoice.uservoicesdk.Session;
16 | import com.uservoice.uservoicesdk.activity.InstantAnswersActivity;
17 | import com.uservoice.uservoicesdk.deflection.Deflection;
18 | import com.uservoice.uservoicesdk.flow.SigninCallback;
19 | import com.uservoice.uservoicesdk.flow.SigninManager;
20 | import com.uservoice.uservoicesdk.model.Suggestion;
21 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
22 | import com.uservoice.uservoicesdk.ui.Utils;
23 |
24 | @SuppressLint("ValidFragment")
25 | public class SubscribeDialogFragment extends DialogFragmentBugfixed {
26 |
27 | private final Suggestion suggestion;
28 | private final SuggestionDialogFragment suggestionDialog;
29 | private final String deflectingType;
30 |
31 | public SubscribeDialogFragment(Suggestion suggestion, SuggestionDialogFragment suggestionDialog, String deflectingType) {
32 | this.suggestion = suggestion;
33 | this.suggestionDialog = suggestionDialog;
34 | this.deflectingType = deflectingType;
35 | }
36 |
37 | @Override
38 | public Dialog onCreateDialog(Bundle savedInstanceState) {
39 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
40 | builder.setTitle(R.string.uv_subscribe_dialog_title);
41 | if (!Utils.isDarkTheme(getActivity())) {
42 | builder.setInverseBackgroundForced(true);
43 | }
44 | View view = getActivity().getLayoutInflater().inflate(R.layout.uv_subscribe_dialog, null);
45 | final EditText emailField = (EditText) view.findViewById(R.id.uv_email);
46 | emailField.setText(Session.getInstance().getEmail(getActivity()));
47 | builder.setView(view);
48 | builder.setNegativeButton(R.string.uv_nevermind, null);
49 | builder.setPositiveButton(R.string.uv_subscribe, null);
50 |
51 | final AlertDialog dialog = builder.create();
52 | dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
53 | dialog.setOnShowListener(new DialogInterface.OnShowListener() {
54 | @Override
55 | public void onShow(DialogInterface d) {
56 | // We override the dialog listener here instead of through the builder so that we can control when the dialog gets dismissed.
57 | // Otherwise, the dialog will always dismiss when the positive button is clicked.
58 | Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
59 | positiveButton.setOnClickListener(new View.OnClickListener() {
60 | @Override
61 | public void onClick(View v) {
62 | String email = emailField.getText().toString();
63 | if (!SigninManager.isValidEmail(email)) {
64 | Toast.makeText(getActivity(), R.string.uv_msg_bad_email_format, Toast.LENGTH_SHORT).show();
65 | } else {
66 | Session.getInstance().persistIdentity(getActivity(), Session.getInstance().getName(getActivity()), email);
67 | SigninManager.signinForSubscribe(getActivity(), Session.getInstance().getEmail(getActivity()), new SigninCallback() {
68 | @Override
69 | public void onSuccess() {
70 | suggestion.subscribe(getActivity(), new DefaultCallback(getActivity()) {
71 | @Override
72 | public void onModel(Suggestion model) {
73 | if (getActivity() instanceof InstantAnswersActivity)
74 | Deflection.trackDeflection(getActivity(), "subscribed", deflectingType, model);
75 | suggestionDialog.suggestionSubscriptionUpdated(model);
76 | dialog.dismiss();
77 | }
78 | });
79 | }
80 | });
81 | }
82 | }
83 | });
84 | }
85 | });
86 | return dialog;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/dialog/UnhelpfulDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.dialog;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 |
9 | import com.uservoice.uservoicesdk.R;
10 | import com.uservoice.uservoicesdk.activity.ContactActivity;
11 | import com.uservoice.uservoicesdk.ui.Utils;
12 |
13 | public class UnhelpfulDialogFragment extends DialogFragmentBugfixed {
14 |
15 | @Override
16 | public Dialog onCreateDialog(Bundle savedInstanceState) {
17 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
18 |
19 | if (!Utils.isDarkTheme(getActivity())) {
20 | builder.setInverseBackgroundForced(true);
21 | }
22 |
23 | builder.setTitle(R.string.uv_unhelpful_article_message_question);
24 |
25 | builder.setNegativeButton(R.string.uv_no, new DialogInterface.OnClickListener() {
26 | @Override
27 | public void onClick(DialogInterface dialog, int which) {
28 | dialog.cancel();
29 | }
30 | });
31 |
32 | builder.setPositiveButton(R.string.uv_yes, new DialogInterface.OnClickListener() {
33 | @Override
34 | public void onClick(DialogInterface dialog, int which) {
35 | getActivity().startActivity(new Intent(getActivity(), ContactActivity.class));
36 | dialog.cancel();
37 | }
38 | });
39 |
40 | return builder.create();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/flow/InitManager.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.flow;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | import com.uservoice.uservoicesdk.Config;
7 | import com.uservoice.uservoicesdk.Session;
8 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
9 | import com.uservoice.uservoicesdk.model.AccessToken;
10 | import com.uservoice.uservoicesdk.model.AccessTokenResult;
11 | import com.uservoice.uservoicesdk.model.BaseModel;
12 | import com.uservoice.uservoicesdk.model.ClientConfig;
13 | import com.uservoice.uservoicesdk.model.RequestToken;
14 | import com.uservoice.uservoicesdk.model.User;
15 | import com.uservoice.uservoicesdk.rest.RestResult;
16 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
17 |
18 | public class InitManager {
19 |
20 | private final Context context;
21 | private final Runnable callback;
22 | private boolean canceled;
23 |
24 | public InitManager(Context context, Runnable callback) {
25 | this.context = context;
26 | this.callback = callback;
27 | }
28 |
29 | public void init() {
30 | if (Session.getInstance().getClientConfig() == null) {
31 | ClientConfig.loadClientConfig(context, new DefaultCallback(context) {
32 | @Override
33 | public void onModel(ClientConfig model) {
34 | Session.getInstance().setClientConfig(model);
35 | // if we are getting the client config, they are launching the ui
36 | // do this here so that we have the subdomain id, so that the channel event works for now
37 | // once babayaga actually supports recording events using the subdomain key, this could be moved back to UserVoice.java
38 | Babayaga.track(context, Babayaga.Event.VIEW_CHANNEL);
39 | loadUser();
40 | }
41 | });
42 | } else {
43 | loadUser();
44 | }
45 | }
46 |
47 | private void loadUser() {
48 | if (Session.getInstance().getUser() == null) {
49 | if (shouldSignIn()) {
50 | RequestToken.getRequestToken(context, new DefaultCallback(context) {
51 | @Override
52 | public void onModel(RequestToken model) {
53 | if (canceled)
54 | return;
55 | Session.getInstance().setRequestToken(model);
56 | Config config = Session.getInstance().getConfig(context);
57 | User.findOrCreate(context, config.getEmail(), config.getName(), config.getGuid(), new DefaultCallback>(context) {
58 | public void onModel(AccessTokenResult model) {
59 | if (canceled)
60 | return;
61 | Session.getInstance().setAccessToken(context, model.getAccessToken());
62 | Session.getInstance().setUser(context, model.getModel());
63 | done();
64 | }
65 |
66 | @Override
67 | public void onError(RestResult error) {
68 | if (error.getType().equals("unauthorized")) {
69 | done();
70 | } else {
71 | super.onError(error);
72 | }
73 | }
74 | });
75 | }
76 | });
77 | } else {
78 | AccessToken accessToken = BaseModel.load(Session.getInstance().getSharedPreferences(context), "access_token", "access_token", AccessToken.class);
79 | if (accessToken != null) {
80 | Session.getInstance().setAccessToken(accessToken);
81 | User.loadCurrentUser(context, new DefaultCallback(context) {
82 | @Override
83 | public void onModel(User model) {
84 | Session.getInstance().setUser(context, model);
85 | done();
86 | }
87 |
88 | @Override
89 | public void onError(RestResult error) {
90 | Session.getInstance().setAccessToken(null);
91 | SharedPreferences.Editor edit = Session.getInstance().getSharedPreferences(context).edit();
92 | edit.remove("access_token");
93 | edit.commit();
94 | loadUser();
95 | }
96 | });
97 | } else {
98 | done();
99 | }
100 | }
101 | } else {
102 | done();
103 | }
104 | }
105 |
106 | private boolean shouldSignIn() {
107 | Config config = Session.getInstance().getConfig(context);
108 | return config.getEmail() != null;
109 | }
110 |
111 | public void cancel() {
112 | canceled = true;
113 | }
114 |
115 | private void done() {
116 | callback.run();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/flow/SigninCallback.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.flow;
2 |
3 | public abstract class SigninCallback {
4 | public abstract void onSuccess();
5 | public void onFailure() {}
6 | }
7 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/flow/SigninManager.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.flow;
2 |
3 | import android.support.v4.app.FragmentActivity;
4 |
5 | import android.text.TextUtils;
6 | import android.widget.Toast;
7 | import com.uservoice.uservoicesdk.R;
8 | import com.uservoice.uservoicesdk.Session;
9 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
10 | import com.uservoice.uservoicesdk.dialog.PasswordDialogFragment;
11 | import com.uservoice.uservoicesdk.dialog.SigninDialogFragment;
12 | import com.uservoice.uservoicesdk.flow.SigninCallback;
13 | import com.uservoice.uservoicesdk.model.AccessTokenResult;
14 | import com.uservoice.uservoicesdk.model.RequestToken;
15 | import com.uservoice.uservoicesdk.model.User;
16 | import com.uservoice.uservoicesdk.rest.Callback;
17 | import com.uservoice.uservoicesdk.rest.RestResult;
18 | import com.uservoice.uservoicesdk.ui.DefaultCallback;
19 |
20 | import java.util.regex.Pattern;
21 |
22 | public class SigninManager {
23 |
24 | private final SigninCallback callback;
25 | private String email;
26 | private String name;
27 | private final FragmentActivity activity;
28 | private boolean passwordOnly;
29 | private static Pattern emailFormat = Pattern.compile("\\A(\\w[-+.\\w!\\#\\$%&'\\*\\+\\-/=\\?\\^_`\\{\\|\\}~]*@([-\\w]*\\.)+[a-zA-Z]{2,9})\\z");
30 |
31 | public static void signIn(FragmentActivity activity, SigninCallback callback) {
32 | new SigninManager(activity, null, null, callback).signIn();
33 | }
34 |
35 | public static void signIn(FragmentActivity activity, String email, String name, SigninCallback callback) {
36 | new SigninManager(activity, email, name, callback).signIn();
37 | }
38 |
39 | public static boolean isValidEmail(String email) {
40 | return !TextUtils.isEmpty(email) && emailFormat.matcher(email).matches();
41 | }
42 |
43 | private SigninManager(FragmentActivity activity, String email, String name, SigninCallback callback) {
44 | this.activity = activity;
45 | this.email = email == null || email.trim().length() == 0 ? null : email;
46 | this.name = name == null || name.trim().length() == 0 ? null : name;
47 | this.callback = callback;
48 | }
49 |
50 | private void signIn() {
51 | User currentUser = Session.getInstance().getUser();
52 | if (currentUser != null && (email == null || email.equals(currentUser.getEmail()))) {
53 | callback.onSuccess();
54 | } else if (Session.getInstance().getAccessToken() != null) {
55 | // If we have an access token but no user, they have signed in in this session. Don't prompt again.
56 | callback.onSuccess();
57 | } else if (!isValidEmail(email)) {
58 | Toast.makeText(activity, R.string.uv_msg_bad_email_format, Toast.LENGTH_SHORT).show();
59 | callback.onFailure();
60 | } else {
61 | email = email == null ? Session.getInstance().getEmail(activity) : email;
62 | name = name == null ? Session.getInstance().getName(activity) : name;
63 | if (email != null) {
64 | User.discover(activity, email, new Callback() {
65 | @Override
66 | public void onModel(User model) {
67 | promptToSignIn();
68 | }
69 |
70 | @Override
71 | public void onError(RestResult error) {
72 | createUser();
73 | }
74 | });
75 | } else {
76 | promptToSignIn();
77 | }
78 | }
79 | }
80 |
81 | private void createUser() {
82 | RequestToken.getRequestToken(activity, new DefaultCallback(activity) {
83 | @Override
84 | public void onModel(RequestToken model) {
85 | Session.getInstance().setRequestToken(model);
86 | User.findOrCreate(activity, email, name, new DefaultCallback>(activity) {
87 | @Override
88 | public void onModel(AccessTokenResult model) {
89 | Session.getInstance().setUser(activity, model.getModel());
90 | Session.getInstance().setAccessToken(activity, model.getAccessToken());
91 | Babayaga.track(activity, Babayaga.Event.IDENTIFY);
92 | callback.onSuccess();
93 | }
94 | });
95 | }
96 | });
97 | }
98 |
99 | private void promptToSignIn() {
100 | if (passwordOnly) {
101 | PasswordDialogFragment dialog = new PasswordDialogFragment(callback);
102 | dialog.show(activity.getSupportFragmentManager(), "PasswordDialogFragment");
103 | } else {
104 | SigninDialogFragment dialog = new SigninDialogFragment(email, name, callback);
105 | dialog.show(activity.getSupportFragmentManager(), "SigninDialogFragment");
106 | }
107 | }
108 |
109 | public void setPasswordOnly(boolean passwordOnly) {
110 | this.passwordOnly = passwordOnly;
111 | }
112 |
113 | public static void signinForSubscribe(FragmentActivity activity, String email, SigninCallback callback) {
114 | SigninManager manager = new SigninManager(activity, email, Session.getInstance().getName(activity), callback);
115 | manager.setPasswordOnly(true);
116 | manager.signIn();
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/image/DownloadImageTask.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.image;
2 |
3 | import java.io.InputStream;
4 |
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.os.AsyncTask;
8 | import android.widget.ImageView;
9 |
10 | public class DownloadImageTask extends AsyncTask {
11 | private ImageView imageView;
12 | private final String url;
13 |
14 | public DownloadImageTask(String url, ImageView imageView) {
15 | this.url = url;
16 | this.imageView = imageView;
17 | imageView.setImageBitmap(null);
18 | }
19 |
20 | protected Bitmap doInBackground(Void... voids) {
21 | Bitmap bitmap = null;
22 | InputStream in = null;
23 |
24 | try {
25 | in = new java.net.URL(url).openStream();
26 | bitmap = BitmapFactory.decodeStream(in);
27 | } catch (Exception e) {
28 | e.printStackTrace();
29 | } finally {
30 | try {
31 | in.close();
32 | } catch (Exception ignored) {
33 | }
34 | }
35 | return bitmap;
36 | }
37 |
38 | protected void onPostExecute(Bitmap bitmap) {
39 | ImageCache.getInstance().set(url, bitmap);
40 | imageView.setImageBitmap(bitmap);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/image/ImageCache.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.image;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | import android.graphics.Bitmap;
9 | import android.widget.ImageView;
10 |
11 | public class ImageCache {
12 |
13 | private static ImageCache instance;
14 |
15 | public static ImageCache getInstance() {
16 | if (instance == null) {
17 | instance = new ImageCache();
18 | }
19 | return instance;
20 | }
21 |
22 | private int capacity = 20;
23 | private Map cache = new HashMap(capacity);
24 | private List mru = new ArrayList();
25 |
26 | public void loadImage(String url, ImageView imageView) {
27 | if (cache.containsKey(url)) {
28 | imageView.setImageBitmap(cache.get(url));
29 | mru.remove(url);
30 | mru.add(url);
31 | } else {
32 | new DownloadImageTask(url, imageView).execute();
33 | }
34 | }
35 |
36 | public void set(String url, Bitmap bitmap) {
37 | if (cache.containsKey(url)) {
38 | cache.put(url, bitmap);
39 | mru.remove(url);
40 | mru.add(url);
41 | } else {
42 | if (cache.size() == capacity) {
43 | String lru = mru.get(0);
44 | cache.remove(lru);
45 | mru.remove(0);
46 | }
47 | cache.put(url, bitmap);
48 | mru.add(url);
49 | }
50 | }
51 |
52 | public void purge() {
53 | cache.clear();
54 | mru.clear();
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/AccessToken.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import com.uservoice.uservoicesdk.Session;
12 | import com.uservoice.uservoicesdk.rest.Callback;
13 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
14 |
15 | public class AccessToken extends BaseModel {
16 |
17 | private String key;
18 | private String secret;
19 |
20 | public static void authorize(Context context, String email, String password, final Callback callback) {
21 | Map params = new HashMap();
22 | params.put("email", email);
23 | params.put("password", password);
24 | params.put("request_token", Session.getInstance().getRequestToken().getKey());
25 | doPost(context, apiPath("/oauth/authorize.json"), params, new RestTaskCallback(callback) {
26 | @Override
27 | public void onComplete(JSONObject result) throws JSONException {
28 | callback.onModel(deserializeObject(result, "token", AccessToken.class));
29 | }
30 | });
31 | }
32 |
33 | @Override
34 | public void load(JSONObject object) throws JSONException {
35 | key = getString(object, "oauth_token");
36 | secret = getString(object, "oauth_token_secret");
37 | }
38 |
39 | public String getKey() {
40 | return key;
41 | }
42 |
43 | public String getSecret() {
44 | return secret;
45 | }
46 |
47 | public void save(JSONObject object) throws JSONException {
48 | object.put("oauth_token", key);
49 | object.put("oauth_token_secret", secret);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/AccessTokenResult.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | public class AccessTokenResult {
4 | private T model;
5 | private AccessToken accessToken;
6 |
7 | public AccessTokenResult(T model, AccessToken accessToken) {
8 | this.model = model;
9 | this.accessToken = accessToken;
10 | }
11 |
12 | public T getModel() {
13 | return model;
14 | }
15 |
16 | public AccessToken getAccessToken() {
17 | return accessToken;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Article.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 |
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | import com.uservoice.uservoicesdk.rest.Callback;
15 | import com.uservoice.uservoicesdk.rest.RestTask;
16 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
17 |
18 | public class Article extends BaseModel implements Parcelable {
19 |
20 | private String title;
21 | private String html;
22 | private String topicName;
23 | private int weight;
24 |
25 | public Article() {}
26 |
27 | public static void loadPage(Context context, int page, final Callback> callback) {
28 | Map params = new HashMap();
29 | params.put("sort", "ordered");
30 | params.put("filter", "published");
31 | params.put("per_page", "50");
32 | params.put("page", String.valueOf(page));
33 | doGet(context, apiPath("/articles.json"), params, new RestTaskCallback(callback) {
34 | @Override
35 | public void onComplete(JSONObject result) throws JSONException {
36 | callback.onModel(deserializeList(result, "articles", Article.class));
37 | }
38 | });
39 | }
40 |
41 | public static void loadPageForTopic(Context context, int topicId, int page, final Callback> callback) {
42 | Map params = new HashMap();
43 | params.put("sort", "ordered");
44 | params.put("filter", "published");
45 | params.put("per_page", "50");
46 | params.put("page", String.valueOf(page));
47 | doGet(context, apiPath("/topics/%d/articles.json", topicId), params, new RestTaskCallback(callback) {
48 | @Override
49 | public void onComplete(JSONObject result) throws JSONException {
50 | callback.onModel(deserializeList(result, "articles", Article.class));
51 | }
52 | });
53 | }
54 |
55 | public static RestTask loadInstantAnswers(Context context, String query, final Callback> callback) {
56 | Map params = new HashMap<>();
57 | params.put("per_page", "3");
58 | params.put("forum_id", String.valueOf(getConfig(context).getForumId()));
59 | params.put("query", query);
60 | if (getConfig(context).getTopicId() != -1) {
61 | params.put("topic_id", String.valueOf(getConfig(context).getTopicId()));
62 | }
63 | return doGet(context, apiPath("/instant_answers/search.json"), params, new RestTaskCallback(callback) {
64 | @Override
65 | public void onComplete(JSONObject result) throws JSONException {
66 | callback.onModel(deserializeHeterogenousList(result, "instant_answers"));
67 | }
68 | });
69 | }
70 |
71 | @Override
72 | public void load(JSONObject object) throws JSONException {
73 | super.load(object);
74 | title = getString(object, "question");
75 | html = getHtml(object, "answer_html");
76 | if (object.has("normalized_weight")) {
77 | weight = object.getInt("normalized_weight");
78 | }
79 | if (!object.isNull("topic")) {
80 | JSONObject topic = object.getJSONObject("topic");
81 | topicName = getString(topic, "name");
82 | }
83 | }
84 |
85 | public String getTitle() {
86 | return title;
87 | }
88 |
89 | public String getHtml() {
90 | return html;
91 | }
92 |
93 | public String getTopicName() {
94 | return topicName;
95 | }
96 |
97 | public int getWeight() {
98 | return weight;
99 | }
100 |
101 | //
102 | // Parcelable
103 | //
104 |
105 | public int describeContents() {
106 | return 0;
107 | }
108 |
109 | public void writeToParcel(Parcel out, int flags) {
110 | out.writeInt(id);
111 | out.writeString(title);
112 | out.writeString(html);
113 | out.writeString(topicName);
114 | out.writeInt(weight);
115 | }
116 |
117 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
118 | public Article createFromParcel(Parcel in) {
119 | return new Article(in);
120 | }
121 |
122 | public Article[] newArray(int size) {
123 | return new Article[size];
124 | }
125 | };
126 |
127 | private Article(Parcel in) {
128 | id = in.readInt();
129 | title = in.readString();
130 | html = in.readString();
131 | topicName = in.readString();
132 | weight = in.readInt();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Attachment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | public class Attachment extends BaseModel {
7 |
8 | private String fileName;
9 | private String contentType;
10 | private String data;
11 |
12 | public Attachment(String fileName, String contentType, String data) {
13 | this.fileName = fileName;
14 | this.contentType = contentType;
15 | this.data = data;
16 | }
17 |
18 | public String getFileName() {
19 | return fileName;
20 | }
21 |
22 | public String getContentType() {
23 | return contentType;
24 | }
25 |
26 | public String getData() {
27 | return data;
28 | }
29 |
30 | @Override
31 | public void save(JSONObject object) throws JSONException {
32 | object.put("fileName", fileName);
33 | object.put("contentType", contentType);
34 | object.put("data", data);
35 | }
36 |
37 | @Override
38 | public void load(JSONObject object) throws JSONException {
39 | fileName = getString(object, "fileName");
40 | contentType = getString(object, "contentType");
41 | data = getString(object, "data");
42 | }
43 | }
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Category.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | public class Category extends BaseModel {
7 | private String name;
8 |
9 | @Override
10 | public void load(JSONObject object) throws JSONException {
11 | super.load(object);
12 | name = getString(object, "name");
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | @Override
20 | public String toString() {
21 | return name;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Comment.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.Date;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
14 | import com.uservoice.uservoicesdk.rest.Callback;
15 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
16 |
17 | public class Comment extends BaseModel {
18 | private String text;
19 | private String userName;
20 | private String avatarUrl;
21 | private Date createdAt;
22 |
23 | public static void loadComments(Context context, Suggestion suggestion, int page, final Callback> callback) {
24 | Map params = new HashMap();
25 | params.put("page", String.valueOf(page));
26 | doGet(context, apiPath("/forums/%d/suggestions/%d/comments.json", suggestion.getForumId(), suggestion.getId()), params, new RestTaskCallback(callback) {
27 | @Override
28 | public void onComplete(JSONObject object) throws JSONException {
29 | callback.onModel(deserializeList(object, "comments", Comment.class));
30 | }
31 | });
32 | }
33 |
34 | public static void createComment(final Context context, final Suggestion suggestion, String text, final Callback callback) {
35 | Map params = new HashMap();
36 | params.put("comment[text]", text);
37 | doPost(context, apiPath("/forums/%d/suggestions/%d/comments.json", suggestion.getForumId(), suggestion.getId()), params, new RestTaskCallback(callback) {
38 | @Override
39 | public void onComplete(JSONObject object) throws JSONException {
40 | Babayaga.track(context, Babayaga.Event.COMMENT_IDEA, suggestion.getId());
41 | callback.onModel(deserializeObject(object, "comment", Comment.class));
42 | }
43 | });
44 | }
45 |
46 | @Override
47 | public void load(JSONObject object) throws JSONException {
48 | super.load(object);
49 | text = getString(object, "formatted_text");
50 | JSONObject user = object.getJSONObject("creator");
51 | userName = getString(user, "name");
52 | avatarUrl = getString(user, "avatar_url");
53 | createdAt = getDate(object, "created_at");
54 | }
55 |
56 | public String getText() {
57 | return text;
58 | }
59 |
60 | public String getUserName() {
61 | return userName;
62 | }
63 |
64 | public String getAvatarUrl() {
65 | return avatarUrl;
66 | }
67 |
68 | public Date getCreatedAt() {
69 | return createdAt;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/CustomField.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.json.JSONArray;
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | public class CustomField extends BaseModel {
11 | private String name;
12 | private List predefinedValues;
13 | private boolean required;
14 |
15 | @Override
16 | public void load(JSONObject object) throws JSONException {
17 | super.load(object);
18 | name = getString(object, "name");
19 | required = !object.getBoolean("allow_blank");
20 | predefinedValues = new ArrayList();
21 | if (object.has("possible_values")) {
22 | JSONArray values = object.getJSONArray("possible_values");
23 | for (int i = 0; i < values.length(); i++) {
24 | JSONObject value = values.getJSONObject(i);
25 | predefinedValues.add(getString(value, "value"));
26 | }
27 | }
28 | }
29 |
30 | @Override
31 | public void save(JSONObject object) throws JSONException {
32 | super.save(object);
33 | object.put("name", name);
34 | object.put("allow_blank", !required);
35 | JSONArray jsonPredefinedValues = new JSONArray();
36 | for (String value : predefinedValues) {
37 | JSONObject predefinedValue = new JSONObject();
38 | predefinedValue.put("value", value);
39 | jsonPredefinedValues.put(predefinedValue);
40 | }
41 | object.put("possible_values", jsonPredefinedValues);
42 | }
43 |
44 | public boolean isRequired() {
45 | return required;
46 | }
47 |
48 | public boolean isPredefined() {
49 | return predefinedValues.size() > 0;
50 | }
51 |
52 | public List getPredefinedValues() {
53 | return predefinedValues;
54 | }
55 |
56 | public String getName() {
57 | return name;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Forum.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import com.uservoice.uservoicesdk.rest.Callback;
12 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
13 |
14 | public class Forum extends BaseModel {
15 | private String name;
16 | private int numberOfVotesAllowed;
17 | private int numberOfOpenSuggestions;
18 | private List categories;
19 |
20 | public static void loadForum(Context context, int forumId, final Callback callback) {
21 | doGet(context, apiPath("/forums/%d.json", forumId), new RestTaskCallback(callback) {
22 | @Override
23 | public void onComplete(JSONObject object) throws JSONException {
24 | callback.onModel(deserializeObject(object, "forum", Forum.class));
25 | }
26 | });
27 | }
28 |
29 | @Override
30 | public void load(JSONObject object) throws JSONException {
31 | super.load(object);
32 | name = getString(object, "name");
33 | JSONObject topic = object.getJSONArray("topics").getJSONObject(0);
34 | numberOfOpenSuggestions = topic.getInt("open_suggestions_count");
35 | numberOfVotesAllowed = topic.getInt("votes_allowed");
36 | categories = deserializeList(topic, "categories", Category.class);
37 | if (categories == null)
38 | categories = new ArrayList();
39 | }
40 |
41 | public String getName() {
42 | return name;
43 | }
44 |
45 | public int getNumberOfOpenSuggestions() {
46 | return numberOfOpenSuggestions;
47 | }
48 |
49 | public int getNumberOfVotesAllowed() {
50 | return numberOfVotesAllowed;
51 | }
52 |
53 | public List getCategories() {
54 | return categories;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/RequestToken.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | import com.uservoice.uservoicesdk.rest.Callback;
9 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
10 |
11 | public class RequestToken extends BaseModel {
12 |
13 | private String key;
14 | private String secret;
15 |
16 | public static void getRequestToken(Context context, final Callback callback) {
17 | doGet(context, apiPath("/oauth/request_token.json"), new RestTaskCallback(callback) {
18 | @Override
19 | public void onComplete(JSONObject result) throws JSONException {
20 | callback.onModel(deserializeObject(result, "token", RequestToken.class));
21 | }
22 | });
23 | }
24 |
25 | @Override
26 | public void load(JSONObject object) throws JSONException {
27 | key = getString(object, "oauth_token");
28 | secret = getString(object, "oauth_token_secret");
29 | }
30 |
31 | public String getKey() {
32 | return key;
33 | }
34 |
35 | public String getSecret() {
36 | return secret;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Ticket.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import org.json.JSONException;
10 | import org.json.JSONObject;
11 |
12 | import com.uservoice.uservoicesdk.rest.Callback;
13 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
14 | import com.uservoice.uservoicesdk.babayaga.Babayaga;
15 |
16 | public class Ticket extends BaseModel {
17 |
18 | public static void createTicket(Context context, String message, Map customFields, final Callback callback) {
19 | createTicket(context, message, null, null, customFields, callback);
20 | }
21 |
22 | public static void createTicket(Context context, String message, String email, String name, Map customFields, final Callback callback) {
23 | Map params = new HashMap();
24 | params.put("ticket[message]", message);
25 |
26 | if (email != null)
27 | params.put("email", email);
28 |
29 | if (name != null)
30 | params.put("display_name", name);
31 |
32 | if (Babayaga.getUvts() != null)
33 | params.put("uvts", Babayaga.getUvts());
34 |
35 | for (Map.Entry entry : getSession().getExternalIds().entrySet()) {
36 | params.put(String.format("created_by[external_ids][%s]", entry.getKey()), entry.getValue());
37 | }
38 |
39 | if (getConfig(context).getCustomFields() != null) {
40 | for (Map.Entry entry : getConfig(context).getCustomFields().entrySet()) {
41 | params.put(String.format("ticket[custom_field_values][%s]", entry.getKey()), entry.getValue());
42 | }
43 | }
44 |
45 | if (customFields != null) {
46 | for (Map.Entry entry : customFields.entrySet()) {
47 | params.put(String.format("ticket[custom_field_values][%s]", entry.getKey()), entry.getValue());
48 | }
49 | }
50 |
51 | List attachmentList = getSession().getConfig(context).getAttachmentList();
52 | if (attachmentList != null) {
53 | for (int i = 0; i < attachmentList.size(); i++) {
54 | Attachment attachment = attachmentList.get(i);
55 | params.put(String.format("ticket[attachments][%d][name]", i), attachment.getFileName());
56 | params.put(String.format("ticket[attachments][%d][data]", i), attachment.getData());
57 | params.put(String.format("ticket[attachments][%d][content_type]", i), attachment.getContentType());
58 | }
59 | }
60 |
61 | doPost(context, apiPath("/tickets.json"), params, new RestTaskCallback(callback) {
62 | @Override
63 | public void onComplete(JSONObject result) throws JSONException {
64 | callback.onModel(deserializeObject(result, "ticket", Ticket.class));
65 | }
66 | });
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/Topic.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 | import android.os.Parcel;
5 | import android.os.Parcelable;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import org.json.JSONException;
13 | import org.json.JSONObject;
14 |
15 | import com.uservoice.uservoicesdk.R;
16 | import com.uservoice.uservoicesdk.Session;
17 | import com.uservoice.uservoicesdk.rest.Callback;
18 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
19 |
20 | public class Topic extends BaseModel implements Parcelable {
21 |
22 | protected String name;
23 | private int numberOfArticles;
24 |
25 | public Topic() {}
26 |
27 | public static Topic allArticlesTopic(Context context) {
28 | Topic allArticles = new Topic();
29 | allArticles.name = context.getString(R.string.uv_all_articles);
30 | allArticles.id = -1;
31 | return allArticles;
32 | }
33 |
34 | public static void loadTopics(Context context, final Callback> callback) {
35 | Map params = new HashMap();
36 | params.put("per_page", "100");
37 | doGet(context, apiPath("/topics.json"), params, new RestTaskCallback(callback) {
38 | @Override
39 | public void onComplete(JSONObject object) throws JSONException {
40 | List allTopics = deserializeList(object, "topics", Topic.class);
41 | List topicsWithArticles = new ArrayList(allTopics.size());
42 | for (Topic topic : allTopics) {
43 | if (topic.getNumberOfArticles() > 0)
44 | topicsWithArticles.add(topic);
45 | }
46 | callback.onModel(topicsWithArticles);
47 | }
48 | });
49 | }
50 |
51 | public static void loadTopic(Context context, int topicId, final Callback callback) {
52 | doGet(context, apiPath("/topics/%d.json", topicId), new RestTaskCallback(callback) {
53 | @Override
54 | public void onComplete(JSONObject object) throws JSONException {
55 | callback.onModel(deserializeObject(object, "topic", Topic.class));
56 | }
57 | });
58 | }
59 |
60 | @Override
61 | public void load(JSONObject object) throws JSONException {
62 | super.load(object);
63 | name = getString(object, "name");
64 | numberOfArticles = object.getInt("article_count");
65 | }
66 |
67 | public String getName() {
68 | return name;
69 | }
70 |
71 | public int getNumberOfArticles() {
72 | return numberOfArticles;
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | return name;
78 | }
79 |
80 | //
81 | // Parcelable
82 | //
83 |
84 | public int describeContents() {
85 | return 0;
86 | }
87 |
88 | public void writeToParcel(Parcel out, int flags) {
89 | out.writeInt(id);
90 | out.writeString(name);
91 | out.writeInt(numberOfArticles);
92 | }
93 |
94 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
95 | public Topic createFromParcel(Parcel in) {
96 | return new Topic(in);
97 | }
98 |
99 | public Topic[] newArray(int size) {
100 | return new Topic[size];
101 | }
102 | };
103 |
104 | private Topic(Parcel in) {
105 | id = in.readInt();
106 | name = in.readString();
107 | numberOfArticles = in.readInt();
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/model/User.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.model;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import com.uservoice.uservoicesdk.Session;
12 | import com.uservoice.uservoicesdk.rest.Callback;
13 | import com.uservoice.uservoicesdk.rest.RestTaskCallback;
14 |
15 | public class User extends BaseModel {
16 |
17 | private String name;
18 | private String email;
19 |
20 | public static void discover(Context context, String email, final Callback callback) {
21 | Map params = new HashMap();
22 | params.put("email", email);
23 | doGet(context, apiPath("/users/discover.json"), params, new RestTaskCallback(callback) {
24 | @Override
25 | public void onComplete(JSONObject result) throws JSONException {
26 | callback.onModel(deserializeObject(result, "user", User.class));
27 | }
28 | });
29 | }
30 |
31 | public static void loadCurrentUser(Context context, final Callback callback) {
32 | doGet(context, apiPath("/users/current.json"), new RestTaskCallback(callback) {
33 | @Override
34 | public void onComplete(JSONObject object) throws JSONException {
35 | callback.onModel(deserializeObject(object, "user", User.class));
36 | }
37 | });
38 | }
39 |
40 | public static void findOrCreate(Context context, String email, String name, String guid, final Callback> callback) {
41 | Map params = new HashMap();
42 | params.put("user[display_name]", name);
43 | params.put("user[email]", email);
44 | params.put("user[guid]", guid);
45 | params.put("request_token", Session.getInstance().getRequestToken().getKey());
46 | doPost(context, apiPath("/users/find_or_create.json"), params, new RestTaskCallback(callback) {
47 | @Override
48 | public void onComplete(JSONObject result) throws JSONException {
49 | AccessToken accessToken = deserializeObject(result, "token", AccessToken.class);
50 | User user = deserializeObject(result, "user", User.class);
51 | callback.onModel(new AccessTokenResult(user, accessToken));
52 | }
53 | });
54 | }
55 |
56 | public static void findOrCreate(Context context, String email, String name, final Callback> callback) {
57 | Map params = new HashMap();
58 | params.put("user[display_name]", name);
59 | params.put("user[email]", email);
60 | params.put("request_token", Session.getInstance().getRequestToken().getKey());
61 | doPost(context, apiPath("/users.json"), params, new RestTaskCallback(callback) {
62 | @Override
63 | public void onComplete(JSONObject result) throws JSONException {
64 | AccessToken accessToken = deserializeObject(result, "token", AccessToken.class);
65 | User user = deserializeObject(result, "user", User.class);
66 | callback.onModel(new AccessTokenResult(user, accessToken));
67 | }
68 | });
69 | }
70 |
71 | public static void sendForgotPassword(Context context, String email, final Callback callback) {
72 | Map params = new HashMap();
73 | params.put("user[email]", email);
74 | doGet(context, apiPath("/users/forgot_password.json"), params, new RestTaskCallback(callback) {
75 | @Override
76 | public void onComplete(JSONObject result) throws JSONException {
77 | callback.onModel(deserializeObject(result, "user", User.class));
78 | }
79 | });
80 | }
81 |
82 | @Override
83 | public void load(JSONObject object) throws JSONException {
84 | super.load(object);
85 | name = getString(object, "name");
86 | email = getString(object, "email");
87 | }
88 |
89 | public String getName() {
90 | return name;
91 | }
92 |
93 | public String getEmail() {
94 | return email;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/Callback.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | public abstract class Callback {
4 | public abstract void onModel(T model);
5 |
6 | public abstract void onError(RestResult error);
7 | }
8 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/OkOAuthConsumer.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | import okhttp3.Request;
4 |
5 | import oauth.signpost.AbstractOAuthConsumer;
6 | import oauth.signpost.http.HttpRequest;
7 |
8 | public class OkOAuthConsumer extends AbstractOAuthConsumer {
9 |
10 | public OkOAuthConsumer(String consumerKey, String consumerSecret) {
11 | super(consumerKey, consumerSecret);
12 | }
13 |
14 | @Override
15 | protected HttpRequest wrap(Object request) {
16 | return new OkRequestAdapter((Request) request);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/OkRequestAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | import android.util.Log;
4 |
5 | import okhttp3.Headers;
6 | import okhttp3.Request;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | import oauth.signpost.http.HttpRequest;
15 | import okio.Buffer;
16 |
17 | public class OkRequestAdapter implements HttpRequest {
18 |
19 | private Request request;
20 |
21 | public OkRequestAdapter(Request request) {
22 | this.request = request;
23 | }
24 |
25 | @Override
26 | public String getMethod() {
27 | return request.method();
28 | }
29 |
30 | @Override
31 | public String getRequestUrl() {
32 | return request.url().toString();
33 | }
34 |
35 | @Override
36 | public void setRequestUrl(String url) {
37 | request = new Request.Builder()
38 | .method(request.method(), request.body())
39 | .url(url)
40 | .headers(request.headers())
41 | .build();
42 | }
43 |
44 | @Override
45 | public void setHeader(String name, String value) {
46 | Headers newHeaders = request.headers().newBuilder().add(name, value).build();
47 | request = new Request.Builder()
48 | .headers(newHeaders)
49 | .url(request.url())
50 | .method(request.method(), request.body())
51 | .build();
52 | }
53 |
54 | @Override
55 | public String getHeader(String name) {
56 | return request.header(name);
57 | }
58 |
59 | @Override
60 | public Map getAllHeaders() {
61 | Mapheaders = new HashMap<>();
62 | for (String name : request.headers().names()) {
63 | headers.put(name, request.header(name));
64 | }
65 | return headers;
66 | }
67 |
68 | @Override
69 | public InputStream getMessagePayload() throws IOException {
70 | ByteArrayOutputStream out = new ByteArrayOutputStream((int) request.body().contentLength());
71 | Buffer sink = new Buffer();
72 | request.body().writeTo(sink);
73 | return sink.inputStream();
74 | }
75 |
76 | @Override
77 | public String getContentType() {
78 | if (request.body() != null) {
79 | return request.body().contentType().toString();
80 | }
81 | return null;
82 | }
83 |
84 | @Override
85 | public Object unwrap() {
86 | return request;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/RestMethod.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | public enum RestMethod {
4 | GET, POST, PUT, DELETE
5 | }
6 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/RestResult.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | public class RestResult {
7 |
8 | private Exception exception;
9 | private JSONObject object;
10 | private int statusCode;
11 |
12 | public RestResult(int statusCode, JSONObject object) {
13 | this.statusCode = statusCode;
14 | this.object = object;
15 | }
16 |
17 | public RestResult(Exception exception) {
18 | this.exception = exception;
19 | }
20 |
21 | public RestResult(Exception exception, int statusCode, JSONObject object) {
22 | this.exception = exception;
23 | this.statusCode = statusCode;
24 | this.object = object;
25 | }
26 |
27 | public boolean isError() {
28 | return exception != null || statusCode > 400;
29 | }
30 |
31 | public JSONObject getObject() {
32 | return object;
33 | }
34 |
35 | public Exception getException() {
36 | return exception;
37 | }
38 |
39 | public int getStatusCode() {
40 | return statusCode;
41 | }
42 |
43 | public String getMessage() {
44 | return String.format("%s -- %s", exception == null ? String.valueOf(statusCode) : exception.getMessage(), object);
45 | }
46 |
47 | public String getType() {
48 | try {
49 | return object.getJSONObject("errors").getString("type");
50 | } catch (JSONException e) {
51 | return null;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/RestTask.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.net.URISyntaxException;
5 | import java.util.Locale;
6 | import java.util.Map;
7 |
8 | import oauth.signpost.OAuthConsumer;
9 |
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import android.content.Context;
14 | import android.net.Uri;
15 | import android.os.AsyncTask;
16 | import android.util.Log;
17 |
18 | import okhttp3.FormBody;
19 | import okhttp3.OkHttpClient;
20 | import okhttp3.Request;
21 | import okhttp3.Response;
22 | import com.uservoice.uservoicesdk.Session;
23 | import com.uservoice.uservoicesdk.UserVoice;
24 | import com.uservoice.uservoicesdk.model.AccessToken;
25 |
26 | public class RestTask extends AsyncTask {
27 | private String urlPath;
28 | private RestMethod method;
29 | private Map params;
30 | private RestTaskCallback callback;
31 | private Context context;
32 |
33 | public RestTask(Context context, RestMethod method, String urlPath, Map params, RestTaskCallback callback) {
34 | this.context = context.getApplicationContext();
35 | this.method = method;
36 | this.urlPath = urlPath;
37 | this.callback = callback;
38 | this.params = params;
39 | }
40 |
41 | @Override
42 | protected RestResult doInBackground(String... args) {
43 | try {
44 | Request request = createRequest();
45 | if (isCancelled())
46 | throw new InterruptedException();
47 | OkHttpClient client = new OkHttpClient();
48 | OAuthConsumer consumer = Session.getInstance().getOAuthConsumer(context);
49 | if (consumer != null) {
50 | AccessToken accessToken = Session.getInstance().getAccessToken();
51 | if (accessToken != null) {
52 | consumer.setTokenWithSecret(accessToken.getKey(), accessToken.getSecret());
53 | }
54 | request = (Request) consumer.sign(request).unwrap();
55 | }
56 | Log.d("UV", urlPath);
57 | if (isCancelled())
58 | throw new InterruptedException();
59 | // TODO it would be nice to find a way to abort the request on cancellation
60 | Response response = client.newCall(request).execute();
61 | if (isCancelled())
62 | throw new InterruptedException();
63 | int statusCode = response.code();
64 | String body = response.body().string();
65 | if (statusCode >= 400) {
66 | Log.d("UV", body);
67 | }
68 | if (isCancelled())
69 | throw new InterruptedException();
70 | return new RestResult(statusCode, new JSONObject(body));
71 | } catch (Exception e) {
72 | return new RestResult(e);
73 | }
74 | }
75 |
76 | private Request createRequest() throws URISyntaxException, UnsupportedEncodingException {
77 | Request.Builder builder = new Request.Builder()
78 | .addHeader("Accept-Language", Locale.getDefault().getLanguage())
79 | .addHeader("API-Client", String.format("uservoice-android-%s", UserVoice.getVersion()))
80 | .addHeader("User-Agent", String.format("uservoice-android-%s", UserVoice.getVersion()));
81 |
82 | String host = Session.getInstance().getConfig(context).getSite();
83 | Uri.Builder uriBuilder = new Uri.Builder();
84 | uriBuilder.scheme(host.contains(".us.com") ? "http" : "https");
85 | uriBuilder.encodedAuthority(host);
86 | uriBuilder.path(urlPath);
87 |
88 | if (method == RestMethod.GET || method == RestMethod.DELETE) {
89 | builder.method(method.toString(), null);
90 | addParamsToQueryString(builder, uriBuilder);
91 | } else {
92 | builder.url(uriBuilder.build().toString());
93 | addParamsToBody(builder);
94 | }
95 | return builder.build();
96 | }
97 |
98 | @Override
99 | protected void onPostExecute(RestResult result) {
100 | if (result.isError()) {
101 | callback.onError(result);
102 | } else {
103 | try {
104 | callback.onComplete(result.getObject());
105 | } catch (JSONException e) {
106 | callback.onError(new RestResult(e, result.getStatusCode(), result.getObject()));
107 | }
108 | }
109 | super.onPostExecute(result);
110 | }
111 |
112 | private void addParamsToQueryString(Request.Builder builder, Uri.Builder uriBuilder) throws URISyntaxException {
113 | if (params != null) {
114 | for (Map.Entry param : params.entrySet()) {
115 | uriBuilder.appendQueryParameter(param.getKey(), param.getValue());
116 | }
117 | }
118 | builder.url(uriBuilder.build().toString());
119 | }
120 |
121 | private void addParamsToBody(Request.Builder builder) throws UnsupportedEncodingException, URISyntaxException {
122 | if (params != null) {
123 | FormBody.Builder paramsBuilder = new FormBody.Builder();
124 | for (Map.Entry param : params.entrySet()) {
125 | if (param.getValue() != null) {
126 | paramsBuilder.add(param.getKey(), param.getValue());
127 | }
128 | }
129 | builder.method(method.toString(), paramsBuilder.build());
130 | }
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/rest/RestTaskCallback.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.rest;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | public abstract class RestTaskCallback {
7 |
8 | private final Callback> callback;
9 |
10 | public RestTaskCallback(Callback> callback) {
11 | this.callback = callback;
12 | }
13 |
14 | public abstract void onComplete(JSONObject result) throws JSONException;
15 |
16 | public void onError(RestResult result) {
17 | callback.onError(result);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/DefaultCallback.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.util.Log;
6 |
7 | import com.uservoice.uservoicesdk.R;
8 | import com.uservoice.uservoicesdk.rest.Callback;
9 | import com.uservoice.uservoicesdk.rest.RestResult;
10 |
11 | public abstract class DefaultCallback extends Callback {
12 |
13 | private static final String TAG = "com.uservoice.uservoicesdk";
14 |
15 | private final Context context;
16 |
17 | public DefaultCallback(Context context) {
18 | this.context = context;
19 | }
20 |
21 | @Override
22 | public void onError(RestResult error) {
23 | Log.e(TAG, error.getMessage());
24 | try {
25 | new AlertDialog.Builder(context).setTitle(R.string.uv_network_error).show();
26 | } catch (Exception e) {
27 | // This can happen if the activity is already gone
28 | Log.e(TAG, "Failed trying to show alert: " + e.getMessage());
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/LoadAllAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import android.content.Context;
7 |
8 | public abstract class LoadAllAdapter extends ModelAdapter {
9 |
10 | public LoadAllAdapter(Context context, int layoutId, List objects) {
11 | super(context, layoutId, objects);
12 | loadAll();
13 | }
14 |
15 | private void loadAll() {
16 | loading = true;
17 | notifyDataSetChanged();
18 | loadPage(1, new DefaultCallback>(context) {
19 | @Override
20 | public void onModel(List model) {
21 | objects.addAll(model);
22 | loading = false;
23 | notifyDataSetChanged();
24 | }
25 | });
26 | }
27 |
28 | public void reload() {
29 | if (loading)
30 | return;
31 | objects = new ArrayList();
32 | loadAll();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/ModelAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import java.util.List;
4 |
5 | import android.content.Context;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 |
10 | import com.uservoice.uservoicesdk.R;
11 | import com.uservoice.uservoicesdk.rest.Callback;
12 |
13 | public abstract class ModelAdapter extends SearchAdapter {
14 |
15 | protected static final int MODEL = 0;
16 | protected static final int LOADING = 1;
17 |
18 | protected final int layoutId;
19 | protected LayoutInflater inflater;
20 | protected List objects;
21 | protected int addedObjects = 0;
22 |
23 | public ModelAdapter(Context context, int layoutId, List objects) {
24 | this.context = context;
25 | this.layoutId = layoutId;
26 | this.objects = objects;
27 | inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
28 | }
29 |
30 | @Override
31 | @SuppressWarnings("unchecked")
32 | public View getView(int position, View convertView, ViewGroup parent) {
33 | View view = convertView;
34 | int type = getItemViewType(position);
35 | if (view == null) {
36 | view = inflater.inflate(type == LOADING ? R.layout.uv_loading_item : layoutId, null);
37 | }
38 |
39 | if (type == MODEL) {
40 | T model = (T) getItem(position);
41 | customizeLayout(view, model);
42 | }
43 |
44 | return view;
45 | }
46 |
47 | @Override
48 | public boolean isEnabled(int position) {
49 | return getItemViewType(position) == MODEL;
50 | }
51 |
52 | @Override
53 | public int getItemViewType(int position) {
54 | return position == getObjects().size() ? LOADING : MODEL;
55 | }
56 |
57 | @Override
58 | public int getViewTypeCount() {
59 | return 2;
60 | }
61 |
62 | @Override
63 | public int getCount() {
64 | return getObjects().size() + (loading ? 1 : 0);
65 | }
66 |
67 | @Override
68 | public Object getItem(int position) {
69 | return position < getObjects().size() ? getObjects().get(position) : null;
70 | }
71 |
72 | @Override
73 | public long getItemId(int position) {
74 | return getItemViewType(position) == LOADING ? -1 : position;
75 | }
76 |
77 | protected List getObjects() {
78 | return objects;
79 | }
80 |
81 | public void add(int location, T object) {
82 | objects.add(location, object);
83 | addedObjects += 1;
84 | notifyDataSetChanged();
85 | }
86 |
87 | protected abstract void customizeLayout(View view, T model);
88 |
89 | protected abstract void loadPage(int page, Callback> callback);
90 | }
91 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/PaginatedAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import android.content.Context;
7 |
8 | public abstract class PaginatedAdapter extends ModelAdapter {
9 |
10 | private int page = 1;
11 |
12 | public PaginatedAdapter(Context context, int layoutId, List objects) {
13 | super(context, layoutId, objects);
14 | }
15 |
16 |
17 | public void loadMore() {
18 | if (loading || searchActive || objects.size() == getTotalNumberOfObjects()) return;
19 | loading = true;
20 | notifyDataSetChanged();
21 | loadPage(page, new DefaultCallback>(context) {
22 | @Override
23 | public void onModel(List model) {
24 | objects.addAll(model);
25 | page += 1;
26 | loading = false;
27 | notifyDataSetChanged();
28 | }
29 | });
30 | }
31 |
32 | protected abstract int getTotalNumberOfObjects();
33 |
34 | protected List getObjects() {
35 | return shouldShowSearchResults() ? searchResults : objects;
36 | }
37 |
38 | public void reload() {
39 | // not *correct* but probably good enough. the correct thing would be to cancel the load somehow and proceed
40 | if (loading)
41 | return;
42 | page = 1;
43 | objects = new ArrayList();
44 | loadMore();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/PaginationScrollListener.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import android.widget.AbsListView;
4 |
5 | public class PaginationScrollListener implements AbsListView.OnScrollListener {
6 |
7 | private final PaginatedAdapter> adapter;
8 |
9 | public PaginationScrollListener(PaginatedAdapter> adapter) {
10 | this.adapter = adapter;
11 | }
12 |
13 | @Override
14 | public void onScrollStateChanged(AbsListView view, int scrollState) {
15 | }
16 |
17 | @Override
18 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
19 | if (firstVisibleItem + visibleItemCount >= totalItemCount) {
20 | adapter.loadMore();
21 | }
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/SearchAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.TimerTask;
6 |
7 | import android.content.Context;
8 | import android.widget.BaseAdapter;
9 |
10 | import com.uservoice.uservoicesdk.rest.Callback;
11 | import com.uservoice.uservoicesdk.rest.RestTask;
12 |
13 | public abstract class SearchAdapter extends BaseAdapter {
14 |
15 | protected List searchResults = new ArrayList();
16 | protected boolean searchActive = false;
17 | protected boolean loading;
18 | protected Context context;
19 | protected String currentQuery;
20 | protected String pendingQuery;
21 | protected int scope;
22 | protected SearchTask currentSearch;
23 |
24 | public void performSearch(String query) {
25 | pendingQuery = query;
26 | if (query.length() == 0) {
27 | searchResults = new ArrayList();
28 | loading = false;
29 | notifyDataSetChanged();
30 | } else {
31 | loading = true;
32 | notifyDataSetChanged();
33 | if (currentSearch != null) {
34 | currentSearch.cancel();
35 | }
36 | currentSearch = new SearchTask(query);
37 | currentSearch.run();
38 | }
39 | }
40 |
41 | public void setSearchActive(boolean searchActive) {
42 | this.searchActive = searchActive;
43 | loading = false;
44 | notifyDataSetChanged();
45 | }
46 |
47 | private class SearchTask extends TimerTask {
48 | private final String query;
49 | private boolean stop;
50 | private RestTask task;
51 |
52 | public SearchTask(String query) {
53 | this.query = query;
54 | }
55 |
56 | @Override
57 | public boolean cancel() {
58 | stop = true;
59 | if (task != null) {
60 | task.cancel(true);
61 | }
62 | return true;
63 | }
64 |
65 | @Override
66 | public void run() {
67 | currentQuery = query;
68 | task = search(query, new DefaultCallback>(context) {
69 | @Override
70 | public void onModel(List model) {
71 | if (!stop) {
72 | searchResults = model;
73 | loading = false;
74 | notifyDataSetChanged();
75 | searchResultsUpdated();
76 | }
77 | }
78 | });
79 | if (task == null) {
80 | // can't search
81 | loading = false;
82 | }
83 | }
84 | }
85 |
86 | protected void searchResultsUpdated() {
87 | }
88 |
89 | protected boolean shouldShowSearchResults() {
90 | return searchActive && pendingQuery != null && pendingQuery.length() > 0;
91 | }
92 |
93 | protected RestTask search(String query, Callback> callback) {
94 | return null;
95 | }
96 |
97 | public void setScope(int scope) {
98 | this.scope = scope;
99 | notifyDataSetChanged();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/SearchExpandListener.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.support.v4.view.MenuItemCompat;
5 | import android.view.MenuItem;
6 |
7 | import com.uservoice.uservoicesdk.activity.SearchActivity;
8 |
9 | @SuppressLint("NewApi")
10 | public class SearchExpandListener implements MenuItemCompat.OnActionExpandListener {
11 | private final SearchActivity searchActivity;
12 |
13 | public SearchExpandListener(SearchActivity searchActivity) {
14 | this.searchActivity = searchActivity;
15 | }
16 |
17 | @Override
18 | public boolean onMenuItemActionExpand(MenuItem item) {
19 | searchActivity.getSearchAdapter().setSearchActive(true);
20 | return true;
21 | }
22 |
23 | @Override
24 | public boolean onMenuItemActionCollapse(MenuItem item) {
25 | searchActivity.getSearchAdapter().setSearchActive(false);
26 | searchActivity.hideSearch();
27 | return true;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/SearchQueryListener.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import android.support.v7.widget.SearchView;
4 |
5 | import com.uservoice.uservoicesdk.activity.SearchActivity;
6 |
7 | public class SearchQueryListener implements SearchView.OnQueryTextListener {
8 | private final SearchActivity searchActivity;
9 |
10 | public SearchQueryListener(SearchActivity searchActivity) {
11 | this.searchActivity = searchActivity;
12 | }
13 |
14 | @Override
15 | public boolean onQueryTextSubmit(String query) {
16 | searchActivity.getSearchAdapter().performSearch(query);
17 | return true;
18 | }
19 |
20 | @Override
21 | public boolean onQueryTextChange(String query) {
22 | searchActivity.getSearchAdapter().performSearch(query);
23 | if (query.length() > 0) {
24 | searchActivity.showSearch();
25 | } else {
26 | searchActivity.hideSearch();
27 | }
28 | return true;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/SpinnerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | import java.util.List;
4 |
5 | import android.app.Activity;
6 | import android.graphics.Color;
7 | import android.util.TypedValue;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.BaseAdapter;
12 | import android.widget.TextView;
13 |
14 | import com.uservoice.uservoicesdk.R;
15 |
16 | public class SpinnerAdapter extends BaseAdapter {
17 |
18 | private static int NONE = 0;
19 | private static int OBJECT = 1;
20 |
21 | private final List objects;
22 | private LayoutInflater inflater;
23 | private int color;
24 |
25 | public SpinnerAdapter(Activity context, List objects) {
26 | this.objects = objects;
27 | inflater = context.getLayoutInflater();
28 | TypedValue tv = new TypedValue();
29 | context.getTheme().resolveAttribute(android.R.attr.textColorPrimary, tv, true);
30 | color = context.getResources().getColor(tv.resourceId);
31 | }
32 |
33 | @Override
34 | public int getCount() {
35 | return objects.size() + 1;
36 | }
37 |
38 | @Override
39 | public Object getItem(int position) {
40 | if (position == 0)
41 | return null;
42 | return objects.get(position - 1);
43 | }
44 |
45 | @Override
46 | public long getItemId(int position) {
47 | return 0;
48 | }
49 |
50 | @Override
51 | public int getItemViewType(int position) {
52 | return position == 0 ? NONE : OBJECT;
53 | }
54 |
55 | @Override
56 | public View getDropDownView(int position, View convertView, ViewGroup parent) {
57 | View view = convertView;
58 | int type = getItemViewType(position);
59 | if (view == null) {
60 | view = inflater.inflate(android.R.layout.simple_list_item_1, null);
61 | }
62 |
63 | TextView textView = (TextView) view;
64 | if (type == OBJECT) {
65 | textView.setTextColor(color);
66 | textView.setText(getItem(position).toString());
67 | } else {
68 | textView.setTextColor(Color.GRAY);
69 | textView.setText(R.string.uv_select_none);
70 | }
71 | return view;
72 | }
73 |
74 | @Override
75 | public View getView(int position, View convertView, ViewGroup parent) {
76 | View view = convertView;
77 | int type = getItemViewType(position);
78 | if (view == null) {
79 | view = inflater.inflate(android.R.layout.simple_list_item_1, null);
80 | }
81 |
82 | TextView textView = (TextView) view;
83 | if (type == OBJECT) {
84 | textView.setTextColor(color);
85 | textView.setText(getItem(position).toString());
86 | } else {
87 | textView.setTextColor(color);
88 | textView.setText(R.string.uv_select_one);
89 | }
90 | return view;
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/UserVoiceSDK/src/com/uservoice/uservoicesdk/ui/StickyFocusContainer.java:
--------------------------------------------------------------------------------
1 | package com.uservoice.uservoicesdk.ui;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: austin
6 | * Date: 8/28/13
7 | * Time: 4:43 PM
8 | * To change this template use File | Settings | File Templates.
9 | */
10 | public class StickyFocusContainer {
11 | }
12 |
--------------------------------------------------------------------------------
/android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | allprojects {
3 | repositories {
4 | jcenter()
5 | maven {
6 | url "https://maven.google.com"
7 | }
8 | maven {
9 | url 'https://maven.google.com/'
10 | name 'Google'
11 | }
12 | }
13 | }
14 | buildscript {
15 | repositories {
16 | google()
17 | jcenter()
18 | maven {
19 | url 'https://maven.google.com/'
20 | name 'Google'
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/demo.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uservoice/uservoice-android-sdk/7bd017bf2ca82f037e1703da9b7ada920118daa4/demo.keystore
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':UserVoiceSDK', ':UVDemo'
2 |
--------------------------------------------------------------------------------