diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c3fbff2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 4
+
+[*.{kt,kts}]
+# Match IntelliJ IDEA's default Kotlin formatting rather than ktlint's stricter "official" style.
+ktlint_code_style = intellij_idea
+# Compose @Composable functions use PascalCase and components live in multi-declaration files,
+# which the default naming/filename rules would otherwise flag.
+ktlint_function_naming_ignore_when_annotated_with = Composable
+ktlint_standard_function-naming = disabled
+ktlint_standard_filename = disabled
+
+[*.{yml,yaml,json}]
+indent_size = 2
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..cd329ce
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,47 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Lint & compile
+ runs-on: ubuntu-latest
+ env:
+ # The com.fastcomments artifacts are public-read on Repsy, so these are optional;
+ # they are forwarded if the secrets happen to be configured.
+ REPSY_USERNAME: ${{ secrets.REPSY_USERNAME }}
+ REPSY_PASSWORD: ${{ secrets.REPSY_PASSWORD }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x ./gradlew
+
+ - name: Spotless (formatting check)
+ run: ./gradlew spotlessCheck
+
+ - name: Android Lint
+ run: ./gradlew lintDebug
+
+ - name: Compile (assemble debug)
+ run: ./gradlew assembleDebug
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 90690a4..90f9ae7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,12 +18,17 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
+ lint {
+ // Snapshot of pre-existing issues so CI only fails on newly introduced ones.
+ baseline = file("lint-baseline.xml")
+ }
+
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
+ "proguard-rules.pro",
)
}
}
@@ -79,4 +84,4 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
-}
\ No newline at end of file
+}
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 0000000..2bcc05a
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,652 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/androidTest/java/com/fastcomments/CommentActionsUITests.java b/app/src/androidTest/java/com/fastcomments/CommentActionsUITests.java
index 1114882..ed8e93b 100644
--- a/app/src/androidTest/java/com/fastcomments/CommentActionsUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/CommentActionsUITests.java
@@ -12,10 +12,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-
import com.fastcomments.sdk.R;
-
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -210,8 +207,9 @@ public void testBlockShowsBlockedText() throws Exception {
pollUntil(10000, () -> {
try {
onView(withId(R.id.recyclerViewComments))
- .check(matches(hasDescendant(withText(containsString(
- InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .check(matches(
+ hasDescendant(withText(containsString(InstrumentationRegistry.getInstrumentation()
+ .getTargetContext()
.getString(R.string.you_blocked_this_user))))));
return true;
} catch (Exception | AssertionError e) {
@@ -221,14 +219,14 @@ public void testBlockShowsBlockedText() throws Exception {
// Verify the blocked user placeholder name is shown
onView(withId(R.id.recyclerViewComments))
- .check(matches(hasDescendant(withText(containsString(
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getString(R.string.blocked_user_placeholder))))));
+ .check(matches(hasDescendant(withText(containsString(InstrumentationRegistry.getInstrumentation()
+ .getTargetContext()
+ .getString(R.string.blocked_user_placeholder))))));
// Verify the original comment text is no longer visible
onView(withId(R.id.recyclerViewComments))
- .check(matches(org.hamcrest.Matchers.not(
- hasDescendant(withText(containsString("Block this comment"))))));
+ .check(matches(
+ org.hamcrest.Matchers.not(hasDescendant(withText(containsString("Block this comment"))))));
}
@Test
@@ -263,8 +261,9 @@ public void testUnblockRestoresComment() throws Exception {
pollUntil(10000, () -> {
try {
onView(withId(R.id.recyclerViewComments))
- .check(matches(hasDescendant(withText(containsString(
- InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .check(matches(
+ hasDescendant(withText(containsString(InstrumentationRegistry.getInstrumentation()
+ .getTargetContext()
.getString(R.string.you_blocked_this_user))))));
return true;
} catch (Exception | AssertionError e) {
@@ -293,8 +292,8 @@ public void testUnblockRestoresComment() throws Exception {
// Verify blocked placeholder text is gone
onView(withId(R.id.recyclerViewComments))
.check(matches(org.hamcrest.Matchers.not(
- hasDescendant(withText(containsString(
- InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getString(R.string.you_blocked_this_user)))))));
+ hasDescendant(withText(containsString(InstrumentationRegistry.getInstrumentation()
+ .getTargetContext()
+ .getString(R.string.you_blocked_this_user)))))));
}
}
diff --git a/app/src/androidTest/java/com/fastcomments/CommentCRUDUITests.java b/app/src/androidTest/java/com/fastcomments/CommentCRUDUITests.java
index a8a94ee..2b6190c 100644
--- a/app/src/androidTest/java/com/fastcomments/CommentCRUDUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/CommentCRUDUITests.java
@@ -1,10 +1,9 @@
package com.fastcomments;
import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.clearText;
+import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
-import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
@@ -16,11 +15,8 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,10 +72,8 @@ public void testTypeAndSubmitComment() throws Exception {
}
});
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("Hello from UI test"), closeSoftKeyboard());
- onView(withId(R.id.sendButton))
- .perform(click());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("Hello from UI test"), closeSoftKeyboard());
+ onView(withId(R.id.sendButton)).perform(click());
boolean found = false;
long deadline = System.currentTimeMillis() + 15000;
@@ -111,8 +105,7 @@ public void testEditCommentViaMenu() throws Exception {
}
});
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("Original text"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("Original text"), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
// Wait for comment to appear
@@ -134,9 +127,7 @@ public void testEditCommentViaMenu() throws Exception {
onView(withId(R.id.editCommentText))
.inRoot(isDialog())
.perform(clearText(), typeText("Edited text"), closeSoftKeyboard());
- onView(withId(R.id.saveEditButton))
- .inRoot(isDialog())
- .perform(click());
+ onView(withId(R.id.saveEditButton)).inRoot(isDialog()).perform(click());
// Verify edited text appears
boolean found = false;
@@ -169,8 +160,7 @@ public void testDeleteCommentViaMenu() throws Exception {
}
});
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("Delete me"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("Delete me"), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
// Wait for comment to appear
@@ -189,9 +179,7 @@ public void testDeleteCommentViaMenu() throws Exception {
onView(withText(R.string.delete_comment)).perform(click());
// Confirm in AlertDialog — positive button is R.string.delete ("Delete"), not R.string.delete_comment
- onView(withText(R.string.delete))
- .inRoot(isDialog())
- .perform(click());
+ onView(withText(R.string.delete)).inRoot(isDialog()).perform(click());
Thread.sleep(1000); // Let dialog dismiss and delete API complete
// Verify comment disappeared — check that the empty state reappears
@@ -260,8 +248,8 @@ public void testPaginationLoadsMore() throws Exception {
try {
// Try scrolling to the last adapter position to find older comments
onView(withId(R.id.recyclerViewComments))
- .perform(androidx.test.espresso.contrib.RecyclerViewActions
- .scrollToPosition(34)); // 0-indexed, position 34 = 35th item
+ .perform(androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition(
+ 34)); // 0-indexed, position 34 = 35th item
Thread.sleep(500);
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Comment 1")))));
diff --git a/app/src/androidTest/java/com/fastcomments/FeedUserA_UITests.java b/app/src/androidTest/java/com/fastcomments/FeedUserA_UITests.java
index 90d7bf9..e42aee3 100644
--- a/app/src/androidTest/java/com/fastcomments/FeedUserA_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/FeedUserA_UITests.java
@@ -13,15 +13,13 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Observer role for feed — runs on Emulator A.
* Phase 1: UserB creates a post via SDK, UserA sees the live banner via WebSocket and taps it.
@@ -94,7 +92,9 @@ public void testFeed_UserA() throws Exception {
onView(withId(R.id.newPostsBanner)).check(matches(isDisplayed()));
bannerVisible = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Banner visible: " + bannerVisible);
assertTrue("New posts banner should appear when UserB posts", bannerVisible);
@@ -109,11 +109,12 @@ public void testFeed_UserA() throws Exception {
deadline = System.currentTimeMillis() + 15000;
while (System.currentTimeMillis() < deadline) {
try {
- onView(withId(R.id.recyclerViewFeed))
- .check(matches(hasDescendant(withText(containsString(postText)))));
+ onView(withId(R.id.recyclerViewFeed)).check(matches(hasDescendant(withText(containsString(postText)))));
found = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Phase 1 result: " + found);
assertTrue("UserB's post should appear after tapping new posts banner", found);
@@ -122,8 +123,7 @@ public void testFeed_UserA() throws Exception {
// --- Phase 2: UserA posts via UI, UserB sees it ---
Log.d(TAG, "=== Phase 2: Create post for UserB ===");
String myPostText = "Feed post from A " + System.currentTimeMillis();
- onView(withId(R.id.postContentEditText))
- .perform(click(), typeText(myPostText), closeSoftKeyboard());
+ onView(withId(R.id.postContentEditText)).perform(click(), typeText(myPostText), closeSoftKeyboard());
onView(withId(R.id.submitPostButton)).perform(click());
Log.d(TAG, "Submitted post via UI");
diff --git a/app/src/androidTest/java/com/fastcomments/FeedUserB_UITests.java b/app/src/androidTest/java/com/fastcomments/FeedUserB_UITests.java
index fe742fb..626982f 100644
--- a/app/src/androidTest/java/com/fastcomments/FeedUserB_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/FeedUserB_UITests.java
@@ -13,16 +13,14 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Actor role for feed — runs on Emulator B.
* Phase 1: UserB creates a post via SDK, UserA verifies receipt.
@@ -79,8 +77,7 @@ public void testFeed_UserB() throws Exception {
});
String postText = "Feed post from B " + System.currentTimeMillis();
- onView(withId(R.id.postContentEditText))
- .perform(click(), typeText(postText), closeSoftKeyboard());
+ onView(withId(R.id.postContentEditText)).perform(click(), typeText(postText), closeSoftKeyboard());
onView(withId(R.id.submitPostButton)).perform(click());
Log.d(TAG, "Submitted post via UI");
@@ -107,7 +104,9 @@ public void testFeed_UserB() throws Exception {
onView(withId(R.id.newPostsBanner)).check(matches(isDisplayed()));
bannerVisible = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Banner visible: " + bannerVisible);
assertTrue("New posts banner should appear when UserA posts", bannerVisible);
@@ -126,7 +125,9 @@ public void testFeed_UserB() throws Exception {
.check(matches(hasDescendant(withText(containsString(userAPostText)))));
found = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Phase 2 result: " + found);
assertTrue("UserA's post should appear in UserB's feed", found);
diff --git a/app/src/androidTest/java/com/fastcomments/LiveChatPaginationUITests.java b/app/src/androidTest/java/com/fastcomments/LiveChatPaginationUITests.java
index 75768b7..68a60bf 100644
--- a/app/src/androidTest/java/com/fastcomments/LiveChatPaginationUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/LiveChatPaginationUITests.java
@@ -13,14 +13,11 @@
import android.util.Log;
import android.view.View;
import android.widget.TextView;
-
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
@@ -70,23 +67,33 @@ public void testLoadOlderMessages() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("msg-" + TOTAL_MESSAGES)))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
Log.d(TAG, "Latest message visible");
// Check that not all messages loaded — pagination should be needed
final int[] adapterCount = {0};
onView(withId(R.id.recyclerViewComments)).perform(new ViewAction() {
- @Override public Matcher getConstraints() { return org.hamcrest.Matchers.any(View.class); }
- @Override public String getDescription() { return "read adapter count"; }
- @Override public void perform(UiController uc, View v) {
+ @Override
+ public Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "read adapter count";
+ }
+
+ @Override
+ public void perform(UiController uc, View v) {
RecyclerView rv = (RecyclerView) v;
adapterCount[0] = rv.getAdapter() != null ? rv.getAdapter().getItemCount() : 0;
}
});
Log.d(TAG, "Initial adapter count: " + adapterCount[0]);
- assertTrue("Should have fewer items than total seeded (pagination needed)",
- adapterCount[0] < TOTAL_MESSAGES);
+ assertTrue("Should have fewer items than total seeded (pagination needed)", adapterCount[0] < TOTAL_MESSAGES);
int countBefore = adapterCount[0];
@@ -95,12 +102,9 @@ public void testLoadOlderMessages() throws Exception {
Log.d(TAG, "Pagination buttons correctly hidden");
// Scroll to the top to trigger infinite scroll loading of older messages
- onView(withId(R.id.recyclerViewComments)).perform(
- androidx.test.espresso.action.ViewActions.swipeDown());
- onView(withId(R.id.recyclerViewComments)).perform(
- androidx.test.espresso.action.ViewActions.swipeDown());
- onView(withId(R.id.recyclerViewComments)).perform(
- androidx.test.espresso.action.ViewActions.swipeDown());
+ onView(withId(R.id.recyclerViewComments)).perform(androidx.test.espresso.action.ViewActions.swipeDown());
+ onView(withId(R.id.recyclerViewComments)).perform(androidx.test.espresso.action.ViewActions.swipeDown());
+ onView(withId(R.id.recyclerViewComments)).perform(androidx.test.espresso.action.ViewActions.swipeDown());
Log.d(TAG, "Swiped up to trigger infinite scroll");
// Poll for adapter count to increase
@@ -109,9 +113,18 @@ public void testLoadOlderMessages() throws Exception {
long deadline = System.currentTimeMillis() + 15000;
while (System.currentTimeMillis() < deadline) {
onView(withId(R.id.recyclerViewComments)).perform(new ViewAction() {
- @Override public Matcher getConstraints() { return org.hamcrest.Matchers.any(View.class); }
- @Override public String getDescription() { return "read adapter count after scroll"; }
- @Override public void perform(UiController uc, View v) {
+ @Override
+ public Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "read adapter count after scroll";
+ }
+
+ @Override
+ public void perform(UiController uc, View v) {
RecyclerView rv = (RecyclerView) v;
countAfter[0] = rv.getAdapter() != null ? rv.getAdapter().getItemCount() : 0;
}
@@ -146,7 +159,9 @@ public void testMessagesInOldestFirstOrder() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("message")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
// In live chat (oldest-first), position 0 should be the oldest message.
@@ -155,9 +170,18 @@ public void testMessagesInOldestFirstOrder() throws Exception {
final String[] firstCommentText = {""};
final String[] lastCommentText = {""};
onView(withId(R.id.recyclerViewComments)).perform(new ViewAction() {
- @Override public Matcher getConstraints() { return org.hamcrest.Matchers.any(View.class); }
- @Override public String getDescription() { return "Read first and last comment text"; }
- @Override public void perform(UiController uiController, View view) {
+ @Override
+ public Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Read first and last comment text";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
RecyclerView rv = (RecyclerView) view;
int count = rv.getAdapter() != null ? rv.getAdapter().getItemCount() : 0;
// Find first comment
@@ -186,9 +210,11 @@ public void testMessagesInOldestFirstOrder() throws Exception {
});
Log.d(TAG, "First comment: " + firstCommentText[0] + ", Last comment: " + lastCommentText[0]);
- assertTrue("First comment should be 'First message' (oldest-first chat order)",
+ assertTrue(
+ "First comment should be 'First message' (oldest-first chat order)",
firstCommentText[0].contains("First message"));
- assertTrue("Last comment should be 'Third message' (oldest-first chat order)",
+ assertTrue(
+ "Last comment should be 'Third message' (oldest-first chat order)",
lastCommentText[0].contains("Third message"));
}
}
diff --git a/app/src/androidTest/java/com/fastcomments/LiveChatUserA_UITests.java b/app/src/androidTest/java/com/fastcomments/LiveChatUserA_UITests.java
index f6d9bc4..0cbfe00 100644
--- a/app/src/androidTest/java/com/fastcomments/LiveChatUserA_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/LiveChatUserA_UITests.java
@@ -13,16 +13,13 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Observer role for live chat — runs on Emulator A.
* Phase 1: UserB sends a message, UserA verifies it appears live.
@@ -66,7 +63,9 @@ public void testLiveChat_UserA() throws Exception {
try {
onView(withId(R.id.connectionStatusText)).check(matches(withText(R.string.live_chat_live)));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
Log.d(TAG, "Connected");
@@ -87,7 +86,9 @@ public void testLiveChat_UserA() throws Exception {
.check(matches(hasDescendant(withText(containsString(messageText)))));
found = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Phase 1 result: " + found);
assertTrue("UserB's message should appear in UserA's chat", found);
@@ -100,7 +101,9 @@ public void testLiveChat_UserA() throws Exception {
onView(withId(R.id.userCountText)).check(matches(isDisplayed()));
userCountVisible = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "User count visible: " + userCountVisible);
assertTrue("User count should be visible in header", userCountVisible);
@@ -108,8 +111,7 @@ public void testLiveChat_UserA() throws Exception {
// --- Phase 2: UserA sends, UserB receives ---
Log.d(TAG, "=== Phase 2: Send message to UserB ===");
String myMessage = "Hello from A " + System.currentTimeMillis();
- onView(withId(R.id.commentInput))
- .perform(click(), typeText(myMessage), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText(myMessage), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
// Verify own message appears
@@ -121,7 +123,9 @@ public void testLiveChat_UserA() throws Exception {
.check(matches(hasDescendant(withText(containsString(myMessage)))));
ownAppeared = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
assertTrue("Own message should appear after sending", ownAppeared);
diff --git a/app/src/androidTest/java/com/fastcomments/LiveChatUserB_UITests.java b/app/src/androidTest/java/com/fastcomments/LiveChatUserB_UITests.java
index c753771..bd53aa2 100644
--- a/app/src/androidTest/java/com/fastcomments/LiveChatUserB_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/LiveChatUserB_UITests.java
@@ -6,24 +6,20 @@
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Actor role for live chat — runs on Emulator B.
* Phase 1: UserB sends a message, UserA verifies receipt.
@@ -69,12 +65,13 @@ public void testLiveChat_UserB() throws Exception {
try {
onView(withId(R.id.connectionStatusText)).check(matches(withText(R.string.live_chat_live)));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
String messageText = "Chat from B " + System.currentTimeMillis();
- onView(withId(R.id.commentInput))
- .perform(click(), typeText(messageText), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText(messageText), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
// Verify own message appears
@@ -86,7 +83,9 @@ public void testLiveChat_UserB() throws Exception {
.check(matches(hasDescendant(withText(containsString(messageText)))));
ownAppeared = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
assertTrue("Own message should appear after sending", ownAppeared);
@@ -112,7 +111,9 @@ public void testLiveChat_UserB() throws Exception {
.check(matches(hasDescendant(withText(containsString(userAMessage)))));
found = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Phase 2 result: " + found);
assertTrue("UserA's message should appear live in UserB's chat", found);
diff --git a/app/src/androidTest/java/com/fastcomments/LiveEventUserA_UITests.java b/app/src/androidTest/java/com/fastcomments/LiveEventUserA_UITests.java
index 81c61a9..91fd596 100644
--- a/app/src/androidTest/java/com/fastcomments/LiveEventUserA_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/LiveEventUserA_UITests.java
@@ -7,21 +7,17 @@
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Observer role — runs on Emulator A.
* Tests 6 phases of live event behavior via WebSocket.
@@ -90,7 +86,9 @@ public void testLiveEvents_UserA() throws Exception {
.check(matches(hasDescendant(withText(containsString(commentText)))));
found = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
Log.d(TAG, "Phase 1 result: " + found);
assertTrue("Live comment should appear via WebSocket", found);
@@ -114,7 +112,9 @@ public void testLiveEvents_UserA() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Vote target from A")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
// NOW signal UserB to vote (WS should be connected by now)
@@ -130,19 +130,32 @@ public void testLiveEvents_UserA() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString("Vote target from A"))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
return org.hamcrest.Matchers.any(android.view.View.class);
}
- @Override public String getDescription() { return "check vote count"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+
+ @Override
+ public String getDescription() {
+ return "check vote count";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
android.widget.TextView voteCount = v.findViewById(R.id.upVoteCount);
- if (voteCount != null && !"0".equals(voteCount.getText().toString())) {
+ if (voteCount != null
+ && !"0"
+ .equals(voteCount
+ .getText()
+ .toString())) {
voteChanged[0] = true;
}
}
}));
if (voteChanged[0]) break;
- } catch (Exception | AssertionError e) { /* retry */ }
+ } catch (Exception | AssertionError e) {
+ /* retry */
+ }
Thread.sleep(250);
}
Log.d(TAG, "Phase 2 result: " + voteChanged[0]);
@@ -164,7 +177,9 @@ public void testLiveEvents_UserA() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Offline user C comment")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
// Signal UserB to join (UserA's WS should be connected by now)
@@ -180,19 +195,29 @@ public void testLiveEvents_UserA() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString(commentText))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
return org.hamcrest.Matchers.any(android.view.View.class);
}
- @Override public String getDescription() { return "check online indicator on UserB comment"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+
+ @Override
+ public String getDescription() {
+ return "check online indicator on UserB comment";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
android.view.View indicator = v.findViewById(R.id.onlineIndicator);
- if (indicator != null && indicator.getVisibility() == android.view.View.VISIBLE) {
+ if (indicator != null
+ && indicator.getVisibility() == android.view.View.VISIBLE) {
userBOnline[0] = true;
}
}
}));
if (userBOnline[0]) break;
- } catch (Exception | AssertionError e) { /* retry */ }
+ } catch (Exception | AssertionError e) {
+ /* retry */
+ }
Thread.sleep(250);
}
Log.d(TAG, "Phase 3 UserB online: " + userBOnline[0]);
@@ -204,13 +229,21 @@ public void testLiveEvents_UserA() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString("Offline user C comment"))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
return org.hamcrest.Matchers.any(android.view.View.class);
}
- @Override public String getDescription() { return "check online indicator on UserC comment"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+
+ @Override
+ public String getDescription() {
+ return "check online indicator on UserC comment";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
android.view.View indicator = v.findViewById(R.id.onlineIndicator);
- userCIndicatorOff[0] = indicator == null || indicator.getVisibility() != android.view.View.VISIBLE;
+ userCIndicatorOff[0] =
+ indicator == null || indicator.getVisibility() != android.view.View.VISIBLE;
}
}));
Log.d(TAG, "Phase 3 UserC offline: " + userCIndicatorOff[0]);
@@ -234,7 +267,9 @@ public void testLiveEvents_UserA() throws Exception {
.check(matches(hasDescendant(withText(containsString(deleteText)))));
deleteVisible = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
assertTrue("Comment to delete should be visible", deleteVisible);
@@ -272,7 +307,9 @@ public void testLiveEvents_UserA() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Pin target from A")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
JSONObject phase5Setup = new JSONObject();
@@ -290,9 +327,18 @@ public void testLiveEvents_UserA() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString("Pin target from A"))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() { return org.hamcrest.Matchers.any(android.view.View.class); }
- @Override public String getDescription() { return "check pin icon"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(android.view.View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "check pin icon";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
android.view.View icon = v.findViewById(R.id.pinIcon);
if (icon != null && icon.getVisibility() == android.view.View.VISIBLE) {
pinVisible[0] = true;
@@ -300,7 +346,9 @@ public void testLiveEvents_UserA() throws Exception {
}
}));
if (pinVisible[0]) break;
- } catch (Exception | AssertionError e) { /* retry */ }
+ } catch (Exception | AssertionError e) {
+ /* retry */
+ }
Thread.sleep(250);
}
Log.d(TAG, "Phase 5 result: " + pinVisible[0]);
@@ -321,7 +369,9 @@ public void testLiveEvents_UserA() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Lock target from A")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
JSONObject phase6Setup = new JSONObject();
@@ -339,9 +389,18 @@ public void testLiveEvents_UserA() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString("Lock target from A"))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() { return org.hamcrest.Matchers.any(android.view.View.class); }
- @Override public String getDescription() { return "check lock icon"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(android.view.View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "check lock icon";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
android.view.View icon = v.findViewById(R.id.lockIcon);
if (icon != null && icon.getVisibility() == android.view.View.VISIBLE) {
lockVisible[0] = true;
@@ -349,7 +408,9 @@ public void testLiveEvents_UserA() throws Exception {
}
}));
if (lockVisible[0]) break;
- } catch (Exception | AssertionError e) { /* retry */ }
+ } catch (Exception | AssertionError e) {
+ /* retry */
+ }
Thread.sleep(250);
}
Log.d(TAG, "Phase 6 result: " + lockVisible[0]);
diff --git a/app/src/androidTest/java/com/fastcomments/LiveEventUserB_UITests.java b/app/src/androidTest/java/com/fastcomments/LiveEventUserB_UITests.java
index ea00147..244d006 100644
--- a/app/src/androidTest/java/com/fastcomments/LiveEventUserB_UITests.java
+++ b/app/src/androidTest/java/com/fastcomments/LiveEventUserB_UITests.java
@@ -7,27 +7,21 @@
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.sdk.R;
-
-
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
/**
* Actor role — runs on Emulator B.
* Performs actions in 6 phases that are observed by UserA.
@@ -76,12 +70,13 @@ public void testLiveEvents_UserB() throws Exception {
try {
onView(withId(R.id.commentInput)).check(matches(isDisplayed()));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
String commentText = "Live from B " + System.currentTimeMillis();
- onView(withId(R.id.commentInput))
- .perform(click(), typeText(commentText), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText(commentText), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
boolean ownCommentAppeared = false;
@@ -92,7 +87,9 @@ public void testLiveEvents_UserB() throws Exception {
.check(matches(hasDescendant(withText(containsString(commentText)))));
ownCommentAppeared = true;
break;
- } catch (Exception | AssertionError e) { Thread.sleep(250); }
+ } catch (Exception | AssertionError e) {
+ Thread.sleep(250);
+ }
}
assertTrue("Own comment should appear after posting", ownCommentAppeared);
@@ -114,7 +111,9 @@ public void testLiveEvents_UserB() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString("Vote target from A")))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
// Click the upvote button within the "Vote target" item using RecyclerViewActions
@@ -122,11 +121,18 @@ public void testLiveEvents_UserB() throws Exception {
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
hasDescendant(withText(containsString("Vote target from A"))),
new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
return org.hamcrest.Matchers.any(android.view.View.class);
}
- @Override public String getDescription() { return "click upVoteButton in item"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+
+ @Override
+ public String getDescription() {
+ return "click upVoteButton in item";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
v.findViewById(R.id.upVoteButton).performClick();
}
}));
@@ -142,7 +148,9 @@ public void testLiveEvents_UserB() throws Exception {
try {
onView(withId(R.id.commentInput)).check(matches(isDisplayed()));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
sync.signalReady("phase3");
@@ -170,26 +178,34 @@ public void testLiveEvents_UserB() throws Exception {
onView(withId(R.id.recyclerViewComments))
.check(matches(hasDescendant(withText(containsString(deleteText)))));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
// Click menu on the specific comment using RecyclerViewActions
onView(withId(R.id.recyclerViewComments))
.perform(androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem(
- hasDescendant(withText(containsString(deleteText))),
- new androidx.test.espresso.ViewAction() {
- @Override public org.hamcrest.Matcher getConstraints() { return org.hamcrest.Matchers.any(android.view.View.class); }
- @Override public String getDescription() { return "click menu in item"; }
- @Override public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
+ hasDescendant(withText(containsString(deleteText))), new androidx.test.espresso.ViewAction() {
+ @Override
+ public org.hamcrest.Matcher getConstraints() {
+ return org.hamcrest.Matchers.any(android.view.View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "click menu in item";
+ }
+
+ @Override
+ public void perform(androidx.test.espresso.UiController uc, android.view.View v) {
v.findViewById(R.id.commentMenuButton).performClick();
}
}));
onView(withText(R.string.delete_comment)).perform(click());
// Confirm in AlertDialog
- onView(withText(R.string.delete))
- .inRoot(isDialog())
- .perform(click());
+ onView(withText(R.string.delete)).inRoot(isDialog()).perform(click());
// Wait for comment to disappear from UserB's own view
pollUntil(10000, () -> {
@@ -217,7 +233,9 @@ public void testLiveEvents_UserB() throws Exception {
try {
onView(withId(R.id.recyclerViewComments)).check(matches(isDisplayed()));
return true;
- } catch (Exception | AssertionError e) { return false; }
+ } catch (Exception | AssertionError e) {
+ return false;
+ }
});
pinComment(pinCommentId, ssoTokenBAdmin);
diff --git a/app/src/androidTest/java/com/fastcomments/MentionUITests.java b/app/src/androidTest/java/com/fastcomments/MentionUITests.java
index 07f2e75..2167e95 100644
--- a/app/src/androidTest/java/com/fastcomments/MentionUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/MentionUITests.java
@@ -14,11 +14,8 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,8 +68,7 @@ public void testMentionSearchAndSelect() throws Exception {
});
// Type @Tester a to trigger mention search (should match "Tester alice1")
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("@Tester a"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("@Tester a"), closeSoftKeyboard());
// Poll until the mention popup appears with "Tester alice1"
boolean popupFound = false;
@@ -96,10 +92,8 @@ public void testMentionSearchAndSelect() throws Exception {
.perform(click());
// Type additional text and submit
- onView(withId(R.id.commentInput))
- .perform(typeText("hello!"), closeSoftKeyboard());
- onView(withId(R.id.sendButton))
- .perform(click());
+ onView(withId(R.id.commentInput)).perform(typeText("hello!"), closeSoftKeyboard());
+ onView(withId(R.id.sendButton)).perform(click());
// Verify the posted comment appears with the mentioned username
boolean commentFound = false;
@@ -138,8 +132,7 @@ public void testMentionPopupDismissesOnSpace() throws Exception {
});
// Type @Tester a to trigger mention search
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("@Tester a"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("@Tester a"), closeSoftKeyboard());
// Wait for popup to appear
boolean popupFound = false;
@@ -158,8 +151,7 @@ public void testMentionPopupDismissesOnSpace() throws Exception {
assertTrue("Mention popup should appear before dismissal test", popupFound);
// Type a space to dismiss the mention (triggers cancelMention)
- onView(withId(R.id.commentInput))
- .perform(typeText(" "));
+ onView(withId(R.id.commentInput)).perform(typeText(" "));
// Verify popup is gone
boolean popupDismissed = false;
diff --git a/app/src/androidTest/java/com/fastcomments/PaginationUITests.java b/app/src/androidTest/java/com/fastcomments/PaginationUITests.java
index 98f908a..74058d0 100644
--- a/app/src/androidTest/java/com/fastcomments/PaginationUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/PaginationUITests.java
@@ -10,14 +10,11 @@
import android.view.View;
import android.widget.TextView;
-
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
diff --git a/app/src/androidTest/java/com/fastcomments/PresenceIntegrationTest.java b/app/src/androidTest/java/com/fastcomments/PresenceIntegrationTest.java
index 0aa8bc9..6238263 100644
--- a/app/src/androidTest/java/com/fastcomments/PresenceIntegrationTest.java
+++ b/app/src/androidTest/java/com/fastcomments/PresenceIntegrationTest.java
@@ -1,28 +1,23 @@
package com.fastcomments;
+import static org.junit.Assert.*;
+
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.core.CommentWidgetConfig;
-import com.fastcomments.model.LiveEvent;
import com.fastcomments.pubsub.LiveEventSubscriber;
import com.fastcomments.pubsub.SubscribeToChangesResult;
-
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-
-import static org.junit.Assert.*;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Integration test for the presence update system.
@@ -59,12 +54,11 @@ public void testUserBJoinVisibleToUserA() throws Exception {
List usersJoinedSeen = new CopyOnWriteArrayList<>();
CountDownLatch joinLatch = new CountDownLatch(1);
- subscriberA.setOnConnectionStatusChange((connected, lastEventTime) ->
- Log.d(TAG, "UserA connection: " + connected));
+ subscriberA.setOnConnectionStatusChange(
+ (connected, lastEventTime) -> Log.d(TAG, "UserA connection: " + connected));
SubscribeToChangesResult resultA = subscriberA.subscribeToChanges(
- configA, wsParamsA[0], urlId, wsParamsA[1], wsParamsA[2],
- null, event -> {
+ configA, wsParamsA[0], urlId, wsParamsA[1], wsParamsA[2], null, event -> {
if (event.getType() != null && "p-u".equals(event.getType().getValue())) {
List uj = event.getUj();
if (uj != null) {
@@ -89,8 +83,7 @@ public void testUserBJoinVisibleToUserA() throws Exception {
LiveEventSubscriber subscriberB = LiveEventSubscriber.createTesting();
CommentWidgetConfig configB = new CommentWidgetConfig(testTenantId, urlId);
SubscribeToChangesResult resultB = subscriberB.subscribeToChanges(
- configB, wsParamsB[0], urlId, wsParamsB[1], wsParamsB[2],
- null, event -> {});
+ configB, wsParamsB[0], urlId, wsParamsB[1], wsParamsB[2], null, event -> {});
assertNotNull("UserB should subscribe", resultB);
Log.d(TAG, "UserB subscribed, waiting for UserA to see the join...");
@@ -116,12 +109,11 @@ public void testUserBReconnectVisibleToUserA() throws Exception {
List allJoins = new CopyOnWriteArrayList<>();
CountDownLatch secondJoinLatch = new CountDownLatch(1);
- subscriberA.setOnConnectionStatusChange((connected, lastEventTime) ->
- Log.d(TAG, "UserA connection: " + connected));
+ subscriberA.setOnConnectionStatusChange(
+ (connected, lastEventTime) -> Log.d(TAG, "UserA connection: " + connected));
SubscribeToChangesResult resultA = subscriberA.subscribeToChanges(
- configA, wsParamsA[0], urlId, wsParamsA[1], wsParamsA[2],
- null, event -> {
+ configA, wsParamsA[0], urlId, wsParamsA[1], wsParamsA[2], null, event -> {
if (event.getType() != null && "p-u".equals(event.getType().getValue())) {
List uj = event.getUj();
if (uj != null) {
@@ -145,8 +137,7 @@ public void testUserBReconnectVisibleToUserA() throws Exception {
LiveEventSubscriber subscriberB1 = LiveEventSubscriber.createTesting();
CommentWidgetConfig configB = new CommentWidgetConfig(testTenantId, urlId);
SubscribeToChangesResult resultB1 = subscriberB1.subscribeToChanges(
- configB, wsParamsB[0], urlId, wsParamsB[1], wsParamsB[2],
- null, event -> {});
+ configB, wsParamsB[0], urlId, wsParamsB[1], wsParamsB[2], null, event -> {});
Log.d(TAG, "UserB first connection established");
Thread.sleep(3000); // Let the join propagate
@@ -159,8 +150,7 @@ public void testUserBReconnectVisibleToUserA() throws Exception {
String[] wsParamsB2 = getWsParams(urlId, ssoB);
LiveEventSubscriber subscriberB2 = LiveEventSubscriber.createTesting();
SubscribeToChangesResult resultB2 = subscriberB2.subscribeToChanges(
- configB, wsParamsB2[0], urlId, wsParamsB2[1], wsParamsB2[2],
- null, event -> {});
+ configB, wsParamsB2[0], urlId, wsParamsB2[1], wsParamsB2[2], null, event -> {});
Log.d(TAG, "UserB reconnected, waiting for UserA to see second join...");
boolean sawSecondJoin = secondJoinLatch.await(10, TimeUnit.SECONDS);
@@ -184,10 +174,8 @@ private String[] getWsParams(String urlId, String sso) throws Exception {
String body = resp.body().string();
JSONObject json = new JSONObject(body);
client.dispatcher().executorService().shutdown();
- return new String[]{
- json.optString("tenantIdWS", ""),
- json.optString("urlIdWS", ""),
- json.optString("userIdWS", "")
+ return new String[] {
+ json.optString("tenantIdWS", ""), json.optString("urlIdWS", ""), json.optString("userIdWS", "")
};
}
}
diff --git a/app/src/androidTest/java/com/fastcomments/SyncClient.java b/app/src/androidTest/java/com/fastcomments/SyncClient.java
index ad7ab7b..ec61123 100644
--- a/app/src/androidTest/java/com/fastcomments/SyncClient.java
+++ b/app/src/androidTest/java/com/fastcomments/SyncClient.java
@@ -1,15 +1,13 @@
package com.fastcomments;
-import org.json.JSONObject;
-
import java.io.IOException;
import java.util.concurrent.TimeUnit;
-
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
+import org.json.JSONObject;
/**
* HTTP client for coordinating dual-emulator tests via the sync server.
@@ -30,7 +28,7 @@ public SyncClient(String syncUrl, String role) {
this.role = role;
this.client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
- .readTimeout(180, TimeUnit.SECONDS) // /wait can block up to 120s
+ .readTimeout(180, TimeUnit.SECONDS) // /wait can block up to 120s
.writeTimeout(30, TimeUnit.SECONDS)
.build();
}
diff --git a/app/src/androidTest/java/com/fastcomments/ThreadingUITests.java b/app/src/androidTest/java/com/fastcomments/ThreadingUITests.java
index 23ae8f3..e02a619 100644
--- a/app/src/androidTest/java/com/fastcomments/ThreadingUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/ThreadingUITests.java
@@ -13,11 +13,8 @@
import static org.junit.Assert.assertTrue;
import android.util.Log;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -56,8 +53,7 @@ public void testReplyToComment() throws Exception {
}
});
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("Parent comment"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("Parent comment"), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
// Wait for parent to appear
@@ -86,8 +82,7 @@ public void testReplyToComment() throws Exception {
// Type and submit reply
Log.d(TAG, "Typing reply...");
- onView(withId(R.id.commentInput))
- .perform(click(), typeText("This is a reply"), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText("This is a reply"), closeSoftKeyboard());
Thread.sleep(500); // Let keyboard dismiss
Log.d(TAG, "Clicking send for reply...");
onView(withId(R.id.sendButton)).perform(click());
diff --git a/app/src/androidTest/java/com/fastcomments/UITestBase.java b/app/src/androidTest/java/com/fastcomments/UITestBase.java
index 024d27b..d1afb6a 100644
--- a/app/src/androidTest/java/com/fastcomments/UITestBase.java
+++ b/app/src/androidTest/java/com/fastcomments/UITestBase.java
@@ -1,34 +1,29 @@
package com.fastcomments;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Intent;
import android.os.Bundle;
-
import androidx.test.core.app.ActivityScenario;
import androidx.test.platform.app.InstrumentationRegistry;
-
import com.fastcomments.core.sso.FastCommentsSSO;
import com.fastcomments.core.sso.SecureSSOUserData;
import com.fastcomments.model.APIError;
import com.fastcomments.model.CreateFeedPostParams;
import com.fastcomments.model.FeedPost;
import com.fastcomments.sdk.FCCallback;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.junit.After;
-import org.junit.Before;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
@@ -38,10 +33,10 @@
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
/**
* Base class for UI tests.
@@ -132,8 +127,7 @@ protected void createTestTenant(String email) throws Exception {
// 2. Get tenant ID via e2e test API
Request tenantRequest = new Request.Builder()
- .url(HOST + "/test-e2e/api/tenant/by-email/" + testTenantEmail
- + "?API_KEY=" + e2eApiKey)
+ .url(HOST + "/test-e2e/api/tenant/by-email/" + testTenantEmail + "?API_KEY=" + e2eApiKey)
.get()
.build();
@@ -177,14 +171,14 @@ public void tearDown() throws Exception {
if (testTenantEmail != null && e2eApiKey != null && !e2eApiKey.isEmpty()) {
try {
Request deleteRequest = new Request.Builder()
- .url(HOST + "/test-e2e/api/tenant/by-email/" + testTenantEmail
- + "?API_KEY=" + e2eApiKey)
+ .url(HOST + "/test-e2e/api/tenant/by-email/" + testTenantEmail + "?API_KEY=" + e2eApiKey)
.delete()
.build();
try (Response ignored = httpClient.newCall(deleteRequest).execute()) {
// Best effort
}
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
}
@@ -199,7 +193,8 @@ private void deleteTenantByEmail(String email) {
try (Response ignored = httpClient.newCall(request).execute()) {
// Best effort — tenant may not exist yet
}
- } catch (Exception ignored) {}
+ } catch (Exception ignored) {
+ }
}
// ---- SSO ----
@@ -214,8 +209,7 @@ protected String makeSecureSSOToken(String userId, boolean isAdmin) {
userId,
"tester-" + userId.substring(0, Math.min(8, userId.length())) + "@fctest.com",
"Tester " + userId.substring(0, Math.min(6, userId.length())),
- ""
- );
+ "");
if (isAdmin) {
userData.isAdmin = true;
}
@@ -232,10 +226,7 @@ protected void launchActivity(String urlId, String ssoToken) {
if (scenario != null) {
scenario.close();
}
- Intent intent = new Intent(
- InstrumentationRegistry.getInstrumentation().getTargetContext(),
- TestActivity.class
- );
+ Intent intent = new Intent(InstrumentationRegistry.getInstrumentation().getTargetContext(), TestActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("tenantId", testTenantId);
intent.putExtra("urlId", urlId);
@@ -247,10 +238,8 @@ protected void launchLiveChatActivity(String urlId, String ssoToken) {
if (liveChatScenario != null) {
liveChatScenario.close();
}
- Intent intent = new Intent(
- InstrumentationRegistry.getInstrumentation().getTargetContext(),
- TestLiveChatActivity.class
- );
+ Intent intent =
+ new Intent(InstrumentationRegistry.getInstrumentation().getTargetContext(), TestLiveChatActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("tenantId", testTenantId);
intent.putExtra("urlId", urlId);
@@ -262,10 +251,8 @@ protected void launchFeedActivity(String urlId, String ssoToken) {
if (feedScenario != null) {
feedScenario.close();
}
- Intent intent = new Intent(
- InstrumentationRegistry.getInstrumentation().getTargetContext(),
- TestFeedActivity.class
- );
+ Intent intent =
+ new Intent(InstrumentationRegistry.getInstrumentation().getTargetContext(), TestFeedActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("tenantId", testTenantId);
intent.putExtra("urlId", urlId);
@@ -376,8 +363,7 @@ protected String fetchLatestCommentId(String urlId) {
for (int attempt = 1; attempt <= 3; attempt++) {
try {
Request request = new Request.Builder()
- .url(HOST + "/api/v1/comments?tenantId=" + testTenantId
- + "&urlId=" + urlId + "&limit=1")
+ .url(HOST + "/api/v1/comments?tenantId=" + testTenantId + "&urlId=" + urlId + "&limit=1")
.addHeader("x-api-key", testTenantApiKey)
.get()
.build();
@@ -399,7 +385,10 @@ protected String fetchLatestCommentId(String urlId) {
}
if (attempt < 3) {
- try { Thread.sleep(500); } catch (InterruptedException ignored) {}
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ignored) {
+ }
}
}
return null;
@@ -420,7 +409,8 @@ protected boolean adminUpdateComment(String commentId, JSONObject params) {
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
String body = response.body() != null ? response.body().string() : "";
- fail("adminUpdateComment failed: status=" + response.code() + " body=" + body.substring(0, Math.min(200, body.length())));
+ fail("adminUpdateComment failed: status=" + response.code() + " body="
+ + body.substring(0, Math.min(200, body.length())));
return false;
}
}
@@ -437,16 +427,20 @@ protected void pinComment(String commentId, String adminSsoToken) {
String broadcastId = UUID.randomUUID().toString();
String encodedSso = java.net.URLEncoder.encode(adminSsoToken, "UTF-8");
Request request = new Request.Builder()
- .url(HOST + "/comments/" + testTenantId + "/" + commentId + "/pin"
- + "?broadcastId=" + broadcastId + "&sso=" + encodedSso)
+ .url(HOST + "/comments/" + testTenantId + "/" + commentId + "/pin" + "?broadcastId=" + broadcastId
+ + "&sso=" + encodedSso)
.post(RequestBody.create("", null))
.build();
try (Response response = httpClient.newCall(request).execute()) {
String body = response.body() != null ? response.body().string() : "";
- android.util.Log.d("UITestBase", "pinComment response: " + response.code() + " body=" + body.substring(0, Math.min(200, body.length())));
+ android.util.Log.d(
+ "UITestBase",
+ "pinComment response: " + response.code() + " body="
+ + body.substring(0, Math.min(200, body.length())));
if (!response.isSuccessful()) {
- fail("pinComment failed: status=" + response.code() + " body=" + body.substring(0, Math.min(200, body.length())));
+ fail("pinComment failed: status=" + response.code() + " body="
+ + body.substring(0, Math.min(200, body.length())));
}
}
} catch (Exception e) {
@@ -460,15 +454,16 @@ protected void lockComment(String commentId, String adminSsoToken) {
String broadcastId = UUID.randomUUID().toString();
String encodedSso = java.net.URLEncoder.encode(adminSsoToken, "UTF-8");
Request request = new Request.Builder()
- .url(HOST + "/comments/" + testTenantId + "/" + commentId + "/lock"
- + "?broadcastId=" + broadcastId + "&sso=" + encodedSso)
+ .url(HOST + "/comments/" + testTenantId + "/" + commentId + "/lock" + "?broadcastId=" + broadcastId
+ + "&sso=" + encodedSso)
.post(RequestBody.create("", null))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
String body = response.body() != null ? response.body().string() : "";
- fail("lockComment failed: status=" + response.code() + " body=" + body.substring(0, Math.min(200, body.length())));
+ fail("lockComment failed: status=" + response.code() + " body="
+ + body.substring(0, Math.min(200, body.length())));
}
}
} catch (Exception e) {
@@ -492,7 +487,10 @@ protected void pollUntil(long timeoutMs, PollCondition condition) {
if (System.currentTimeMillis() > deadline) {
return;
}
- try { Thread.sleep(50); } catch (InterruptedException ignored) {}
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ignored) {
+ }
}
}
diff --git a/app/src/androidTest/java/com/fastcomments/VoteUITests.java b/app/src/androidTest/java/com/fastcomments/VoteUITests.java
index b258a64..fa64fcb 100644
--- a/app/src/androidTest/java/com/fastcomments/VoteUITests.java
+++ b/app/src/androidTest/java/com/fastcomments/VoteUITests.java
@@ -13,9 +13,7 @@
import static org.junit.Assert.assertTrue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.sdk.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,14 +45,12 @@ private void postCommentAndWait(String text) throws Exception {
}
});
- onView(withId(R.id.commentInput))
- .perform(click(), typeText(text), closeSoftKeyboard());
+ onView(withId(R.id.commentInput)).perform(click(), typeText(text), closeSoftKeyboard());
onView(withId(R.id.sendButton)).perform(click());
pollUntil(10000, () -> {
try {
- onView(withId(R.id.recyclerViewComments))
- .check(matches(hasDescendant(withText(containsString(text)))));
+ onView(withId(R.id.recyclerViewComments)).check(matches(hasDescendant(withText(containsString(text)))));
return true;
} catch (Exception | AssertionError e) {
return false;
diff --git a/app/src/androidTest/java/com/fastcomments/WebSocketConcurrentTest.java b/app/src/androidTest/java/com/fastcomments/WebSocketConcurrentTest.java
index 7a5c760..0d3b66f 100644
--- a/app/src/androidTest/java/com/fastcomments/WebSocketConcurrentTest.java
+++ b/app/src/androidTest/java/com/fastcomments/WebSocketConcurrentTest.java
@@ -1,14 +1,15 @@
package com.fastcomments;
+import static org.junit.Assert.*;
+
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import okhttp3.*;
-import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Test whether concurrent HTTP API calls kill the WebSocket connection.
@@ -20,8 +21,10 @@ public class WebSocketConcurrentTest {
@Test
public void testWsSurvivesConcurrentHttpCalls() throws Exception {
- String wsUrl = "wss://ws.fastcomments.com/sub?urlId=demo%3Ahttps%3A%2F%2Ffastcomments.com%2F&userIdWS=conctest&tenantIdWS=demo";
- String apiUrl = "https://fastcomments.com/comments/demo/?urlId=https%3A%2F%2Ffastcomments.com%2F&direction=NF&count=5";
+ String wsUrl =
+ "wss://ws.fastcomments.com/sub?urlId=demo%3Ahttps%3A%2F%2Ffastcomments.com%2F&userIdWS=conctest&tenantIdWS=demo";
+ String apiUrl =
+ "https://fastcomments.com/comments/demo/?urlId=https%3A%2F%2Ffastcomments.com%2F&direction=NF&count=5";
// WS client with its own dispatcher and pool (same as SDK's LiveEventSubscriber)
OkHttpClient wsClient = new OkHttpClient.Builder()
@@ -45,13 +48,17 @@ public void testWsSurvivesConcurrentHttpCalls() throws Exception {
public void onOpen(WebSocket ws, Response r) {
Log.d(TAG, "WS OPEN");
}
+
public void onMessage(WebSocket ws, String t) {
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
Log.d(TAG, String.format("[%.1fs] WS MSG: %s", elapsed, t.substring(0, Math.min(60, t.length()))));
}
+
public void onFailure(WebSocket ws, Throwable t, Response r) {
diedAt[0] = System.currentTimeMillis() - start;
- Log.d(TAG, "WS DIED after " + diedAt[0] + "ms: " + t.getClass().getSimpleName() + ": " + t.getMessage());
+ Log.d(
+ TAG,
+ "WS DIED after " + diedAt[0] + "ms: " + t.getClass().getSimpleName() + ": " + t.getMessage());
latch.countDown();
}
});
diff --git a/app/src/androidTest/java/com/fastcomments/WebSocketFromCallbackTest.java b/app/src/androidTest/java/com/fastcomments/WebSocketFromCallbackTest.java
index 1b322c5..dde4ac0 100644
--- a/app/src/androidTest/java/com/fastcomments/WebSocketFromCallbackTest.java
+++ b/app/src/androidTest/java/com/fastcomments/WebSocketFromCallbackTest.java
@@ -1,21 +1,22 @@
package com.fastcomments;
+import static org.junit.Assert.*;
+
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.pubsub.LiveEventSubscriber;
import com.fastcomments.pubsub.SubscribeToChangesResult;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.*;
-import static org.junit.Assert.*;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Calls subscribeToChanges from WITHIN an OkHttp async callback,
@@ -70,7 +71,9 @@ public void onResponse(Call call, Response response) throws IOException {
String tenantIdWS = json.optString("tenantIdWS", "");
String urlIdWS = json.optString("urlIdWS", "");
String userIdWS = json.optString("userIdWS", "");
- Log.d(TAG, "API response on thread: " + Thread.currentThread().getName());
+ Log.d(
+ TAG,
+ "API response on thread: " + Thread.currentThread().getName());
Log.d(TAG, "WS params: " + tenantIdWS + " / " + urlIdWS + " / " + userIdWS);
// Create WS from within THIS callback (OkHttp dispatcher thread)
@@ -86,9 +89,8 @@ public void onResponse(Call call, Response response) throws IOException {
}
});
- SubscribeToChangesResult result = subscriber.subscribeToChanges(
- config, tenantIdWS, urlId, urlIdWS, userIdWS,
- null, event -> {
+ SubscribeToChangesResult result =
+ subscriber.subscribeToChanges(config, tenantIdWS, urlId, urlIdWS, userIdWS, null, event -> {
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
Log.d(TAG, String.format("[%.1fs] Event: %s", elapsed, event.getType()));
});
diff --git a/app/src/androidTest/java/com/fastcomments/WebSocketSdkFlowTest.java b/app/src/androidTest/java/com/fastcomments/WebSocketSdkFlowTest.java
index f019553..bb909da 100644
--- a/app/src/androidTest/java/com/fastcomments/WebSocketSdkFlowTest.java
+++ b/app/src/androidTest/java/com/fastcomments/WebSocketSdkFlowTest.java
@@ -1,25 +1,21 @@
package com.fastcomments;
+import static org.junit.Assert.*;
+
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.pubsub.LiveEventSubscriber;
import com.fastcomments.pubsub.SubscribeToChangesResult;
-
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
-
-import static org.junit.Assert.*;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Uses the actual LiveEventSubscriber.createTesting() and subscribeToChanges()
@@ -91,8 +87,7 @@ public void testSubscribeToChangesDirectly() throws Exception {
event -> {
double elapsed = (System.currentTimeMillis() - start) / 1000.0;
Log.d(TAG, String.format("[%.1fs] Event: type=%s", elapsed, event.getType()));
- }
- );
+ });
assertNotNull("subscribeToChanges should return a result", result);
Log.d(TAG, "subscribeToChanges returned, waiting 30s...");
diff --git a/app/src/debug/java/com/fastcomments/TestActivity.java b/app/src/debug/java/com/fastcomments/TestActivity.java
index fc04c5c..eee964b 100644
--- a/app/src/debug/java/com/fastcomments/TestActivity.java
+++ b/app/src/debug/java/com/fastcomments/TestActivity.java
@@ -1,9 +1,7 @@
package com.fastcomments;
import android.os.Bundle;
-
import androidx.appcompat.app.AppCompatActivity;
-
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.model.SortDirections;
import com.fastcomments.sdk.FastCommentsSDK;
diff --git a/app/src/debug/java/com/fastcomments/TestFeedActivity.java b/app/src/debug/java/com/fastcomments/TestFeedActivity.java
index 8d3d3ff..6bdc038 100644
--- a/app/src/debug/java/com/fastcomments/TestFeedActivity.java
+++ b/app/src/debug/java/com/fastcomments/TestFeedActivity.java
@@ -2,9 +2,7 @@
import android.os.Bundle;
import android.util.Log;
-
import androidx.appcompat.app.AppCompatActivity;
-
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.model.FeedPost;
import com.fastcomments.sdk.FastCommentsFeedSDK;
diff --git a/app/src/debug/java/com/fastcomments/TestLiveChatActivity.java b/app/src/debug/java/com/fastcomments/TestLiveChatActivity.java
index 47b52b5..7db88c6 100644
--- a/app/src/debug/java/com/fastcomments/TestLiveChatActivity.java
+++ b/app/src/debug/java/com/fastcomments/TestLiveChatActivity.java
@@ -1,11 +1,8 @@
package com.fastcomments;
import android.os.Bundle;
-
import androidx.appcompat.app.AppCompatActivity;
-
import com.fastcomments.core.CommentWidgetConfig;
-import com.fastcomments.model.SortDirections;
import com.fastcomments.sdk.FastCommentsSDK;
import com.fastcomments.sdk.LiveChatView;
diff --git a/app/src/main/java/com/fastcomments/DemoBrowserActivity.kt b/app/src/main/java/com/fastcomments/DemoBrowserActivity.kt
index ee11c01..7e80d84 100644
--- a/app/src/main/java/com/fastcomments/DemoBrowserActivity.kt
+++ b/app/src/main/java/com/fastcomments/DemoBrowserActivity.kt
@@ -19,8 +19,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.automirrored.filled.Article
+import androidx.compose.material.icons.automirrored.filled.Chat
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Forum
import androidx.compose.material.icons.filled.GridView
@@ -50,12 +50,12 @@ private data class DemoItem(
val iconColor: Color,
val title: String,
val description: String,
- val activityClass: Class
+ val activityClass: Class,
)
private data class DemoSection(
val title: String,
- val items: List
+ val items: List,
)
private val demoSections = listOf(
@@ -67,23 +67,23 @@ private val demoSections = listOf(
iconColor = Color(0xFF2196F3),
title = "Threaded Comments",
description = "Live threaded commenting with SSO.",
- activityClass = MainActivity::class.java
+ activityClass = MainActivity::class.java,
),
DemoItem(
icon = Icons.AutoMirrored.Filled.Chat,
iconColor = Color(0xFF4CAF50),
title = "Live Chat",
description = "A streaming chat UI optimized for high volume discussions.",
- activityClass = LiveChatExampleActivity::class.java
+ activityClass = LiveChatExampleActivity::class.java,
),
DemoItem(
icon = Icons.Filled.Edit,
iconColor = Color(0xFF3F51B5),
title = "Custom Toolbar",
description = "Global and per-instance custom toolbar buttons",
- activityClass = ToolbarShowcaseActivity::class.java
- )
- )
+ activityClass = ToolbarShowcaseActivity::class.java,
+ ),
+ ),
),
DemoSection(
title = "Feed",
@@ -93,16 +93,16 @@ private val demoSections = listOf(
iconColor = Color(0xFFFF9800),
title = "Social Feed",
description = "Social Feed with SSO Configured",
- activityClass = FeedExampleActivity::class.java
+ activityClass = FeedExampleActivity::class.java,
),
DemoItem(
icon = Icons.Filled.GridView,
iconColor = Color(0xFF9C27B0),
title = "Feed Custom Buttons",
description = "Custom toolbar buttons on the post creation form",
- activityClass = FeedExampleCustomButtonsActivity::class.java
- )
- )
+ activityClass = FeedExampleCustomButtonsActivity::class.java,
+ ),
+ ),
),
DemoSection(
title = "Authentication",
@@ -112,17 +112,17 @@ private val demoSections = listOf(
iconColor = Color(0xFF009688),
title = "Simple SSO",
description = "Client-side SSO for demos and testing",
- activityClass = SimpleSSOExampleActivity::class.java
+ activityClass = SimpleSSOExampleActivity::class.java,
),
DemoItem(
icon = Icons.Filled.Lock,
iconColor = Color(0xFFF44336),
title = "Secure SSO",
description = "Production SSO with server-side token generation",
- activityClass = SecureSSOExampleActivity::class.java
- )
- )
- )
+ activityClass = SecureSSOExampleActivity::class.java,
+ ),
+ ),
+ ),
)
class DemoBrowserActivity : ComponentActivity() {
@@ -133,7 +133,7 @@ class DemoBrowserActivity : ComponentActivity() {
DemoBrowserScreen(
onDemoSelected = { item ->
startActivity(Intent(this, item.activityClass))
- }
+ },
)
}
}
@@ -146,15 +146,15 @@ private fun DemoBrowserScreen(onDemoSelected: (DemoItem) -> Unit) {
Scaffold(
topBar = {
TopAppBar(
- title = { Text("FastComments") }
+ title = { Text("FastComments") },
)
- }
+ },
) { padding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(padding),
- contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
+ contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
demoSections.forEach { section ->
item {
@@ -162,13 +162,13 @@ private fun DemoBrowserScreen(onDemoSelected: (DemoItem) -> Unit) {
text = section.title.uppercase(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
+ modifier = Modifier.padding(top = 24.dp, bottom = 8.dp),
)
}
items(section.items) { demoItem ->
DemoItemRow(
item = demoItem,
- onClick = { onDemoSelected(demoItem) }
+ onClick = { onDemoSelected(demoItem) },
)
}
}
@@ -181,14 +181,14 @@ private fun DemoItemRow(item: DemoItem, onClick: () -> Unit) {
Surface(
onClick = onClick,
shape = MaterialTheme.shapes.medium,
- color = MaterialTheme.colorScheme.surface
+ color = MaterialTheme.colorScheme.surface,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp, horizontal = 4.dp),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(14.dp)
+ horizontalArrangement = Arrangement.spacedBy(14.dp),
) {
Box(
modifier = Modifier
@@ -196,30 +196,30 @@ private fun DemoItemRow(item: DemoItem, onClick: () -> Unit) {
.clip(RoundedCornerShape(8.dp))
.background(
Brush.verticalGradient(
- colors = listOf(item.iconColor, item.iconColor.copy(alpha = 0.8f))
- )
+ colors = listOf(item.iconColor, item.iconColor.copy(alpha = 0.8f)),
+ ),
),
- contentAlignment = Alignment.Center
+ contentAlignment = Alignment.Center,
) {
Icon(
imageVector = item.icon,
contentDescription = null,
tint = Color.White,
- modifier = Modifier.size(18.dp)
+ modifier = Modifier.size(18.dp),
)
}
Column {
Text(
text = item.title,
style = MaterialTheme.typography.bodyMedium,
- fontWeight = FontWeight.SemiBold
+ fontWeight = FontWeight.SemiBold,
)
Text(
text = item.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 2,
- overflow = TextOverflow.Ellipsis
+ overflow = TextOverflow.Ellipsis,
)
}
}
diff --git a/app/src/main/java/com/fastcomments/FeedExampleActivity.java b/app/src/main/java/com/fastcomments/FeedExampleActivity.java
index 5412bf3..b988d4b 100644
--- a/app/src/main/java/com/fastcomments/FeedExampleActivity.java
+++ b/app/src/main/java/com/fastcomments/FeedExampleActivity.java
@@ -5,13 +5,11 @@
import android.os.Looper;
import android.view.View;
import android.widget.Toast;
-
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.core.sso.FastCommentsSSO;
import com.fastcomments.core.sso.SimpleSSOUserData;
@@ -22,13 +20,9 @@
import com.fastcomments.sdk.FeedPostCreateView;
import com.fastcomments.sdk.FollowStateProvider;
import com.fastcomments.sdk.OnUserClickListener;
-import com.fastcomments.sdk.TagSupplier;
-import com.fastcomments.sdk.UserClickContext;
import com.fastcomments.sdk.UserClickSource;
import com.fastcomments.sdk.UserInfo;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
-
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -46,7 +40,7 @@ public class FeedExampleActivity extends AppCompatActivity {
private final Handler mainHandler = new Handler(Looper.getMainLooper());
// Image picker launcher
- private final ActivityResultLauncher pickImageLauncher =
+ private final ActivityResultLauncher pickImageLauncher =
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null && postCreateView != null) {
postCreateView.handleImageResult(uri);
@@ -63,13 +57,12 @@ protected void onCreate(Bundle savedInstanceState) {
config.tenantId = "demo"; // Use your tenant ID here
config.urlId = "https://example.com/page1"; // Use your URL ID here
config.pageTitle = "Feed Example";
-
- // Set up Simple SSO for user authentication. In production you probably want to use SecureSSO and create a token from your server.
+
+ // Set up Simple SSO for user authentication. In production you probably want to use SecureSSO and create a
+ // token from your server.
SimpleSSOUserData userData = new SimpleSSOUserData(
- "Example User",
- "user@example.com",
- "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG");
-
+ "Example User", "user@example.com", "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG");
+
FastCommentsSSO sso = new FastCommentsSSO(userData);
config.sso = sso.prepareToSend();
@@ -86,29 +79,33 @@ public boolean isFollowing(UserInfo user) {
}
@Override
- public void onFollowStateChangeRequested(UserInfo user, boolean desiredFollowing,
- FollowStateCallback resultCallback) {
+ public void onFollowStateChangeRequested(
+ UserInfo user, boolean desiredFollowing, FollowStateCallback resultCallback) {
// Simulate a 2s network round-trip.
- mainHandler.postDelayed(() -> {
- if (desiredFollowing) {
- followedUserIds.add(user.getUserId());
- } else {
- followedUserIds.remove(user.getUserId());
- }
- Toast.makeText(FeedExampleActivity.this,
- (desiredFollowing ? "Followed " : "Unfollowed ") + user.getDisplayName(),
- Toast.LENGTH_SHORT).show();
- resultCallback.onResult(desiredFollowing);
- }, 2000L);
+ mainHandler.postDelayed(
+ () -> {
+ if (desiredFollowing) {
+ followedUserIds.add(user.getUserId());
+ } else {
+ followedUserIds.remove(user.getUserId());
+ }
+ Toast.makeText(
+ FeedExampleActivity.this,
+ (desiredFollowing ? "Followed " : "Unfollowed ") + user.getDisplayName(),
+ Toast.LENGTH_SHORT)
+ .show();
+ resultCallback.onResult(desiredFollowing);
+ },
+ 2000L);
}
});
// Find the feed view in the layout
feedView = findViewById(R.id.feedView);
-
+
// Set the SDK instance for the view
feedView.setSDK(feedSDK);
-
+
feedView.setTagSupplier(currentUser -> {
// You can customize the user's experience. Only feed items with the same tags will be returned.
// return null or don't set a supplier to get a "global" feed.
@@ -131,15 +128,16 @@ public void onFeedLoaded(List posts) {
public void onFeedError(String errorMessage) {
// Error loading feed
// Consumer can handle this error as needed
- Toast.makeText(FeedExampleActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ Toast.makeText(FeedExampleActivity.this, errorMessage, Toast.LENGTH_SHORT)
+ .show();
}
@Override
public void onPostSelected(FeedPost post) {
// User selected a post
- // Here you would typically navigate to a detail view
+ // Here you would typically navigate to a detail view
// or show comments for this post
-
+
// In a real app, you might do:
// navigateToPostDetail(post);
// or
@@ -150,7 +148,7 @@ public void onPostSelected(FeedPost post) {
public void onCommentsRequested(FeedPost post) {
// Show comments dialog for the post from the SDK
CommentsDialog dialog = new CommentsDialog(FeedExampleActivity.this, post, feedSDK);
-
+
// Set comment added listener to update the post in the feed
dialog.setOnCommentAddedListener(postId -> {
// Post stats already updated in feedSDK, just need to refresh UI
@@ -159,13 +157,13 @@ public void onCommentsRequested(FeedPost post) {
feedView.refreshPost(postId);
});
});
-
+
dialog.setOnUserClickListener(userClickListener);
-
+
dialog.show();
}
});
-
+
feedView.setOnUserClickListener(userClickListener);
// Load the feed
@@ -176,9 +174,11 @@ private void setupUserClickListener() {
userClickListener = (context, userInfo, source) -> {
String sourceText = source == UserClickSource.NAME ? "name" : "avatar";
String contextText = context.isComment() ? "comment" : "feed post";
- Toast.makeText(FeedExampleActivity.this,
- "Clicked " + userInfo.getDisplayName() + "'s " + sourceText + " in " + contextText,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ FeedExampleActivity.this,
+ "Clicked " + userInfo.getDisplayName() + "'s " + sourceText + " in " + contextText,
+ Toast.LENGTH_SHORT)
+ .show();
};
}
@@ -197,20 +197,22 @@ private void setupPostCreationView() {
// Create constraints for post creation view (full width at top, floating above content)
ConstraintLayout.LayoutParams postCreateParams = new ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT);
+ ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
postCreateView.setLayoutParams(postCreateParams);
postCreateView.setElevation(16f); // Elevate above other content
-
+
// Add view to parent
parentLayout.addView(postCreateView);
// Update constraints to position the view at the top
ConstraintSet initialConstraintSet = new ConstraintSet();
initialConstraintSet.clone(parentLayout);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.TOP, parentLayout.getId(), ConstraintSet.TOP, 0);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, 0);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.TOP, parentLayout.getId(), ConstraintSet.TOP, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 0);
initialConstraintSet.applyTo(parentLayout);
// Create FAB
@@ -221,8 +223,7 @@ private void setupPostCreationView() {
// Create params for FAB
ConstraintLayout.LayoutParams fabParams = new ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.WRAP_CONTENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT);
+ ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
createPostFab.setLayoutParams(fabParams);
// Add FAB to parent
@@ -231,7 +232,8 @@ private void setupPostCreationView() {
// Update constraints to position the FAB at bottom-end
ConstraintSet fabConstraintSet = new ConstraintSet();
fabConstraintSet.clone(parentLayout);
- fabConstraintSet.connect(createPostFab.getId(), ConstraintSet.BOTTOM, parentLayout.getId(), ConstraintSet.BOTTOM, 32);
+ fabConstraintSet.connect(
+ createPostFab.getId(), ConstraintSet.BOTTOM, parentLayout.getId(), ConstraintSet.BOTTOM, 32);
fabConstraintSet.connect(createPostFab.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 32);
fabConstraintSet.applyTo(parentLayout);
@@ -240,7 +242,7 @@ private void setupPostCreationView() {
// Configure post creation view and prepare it for showing
postCreateView.show();
createPostFab.setVisibility(View.GONE);
-
+
// Apply animation to slide it down
postCreateView.startAnimation(android.view.animation.AnimationUtils.loadAnimation(
FeedExampleActivity.this, com.fastcomments.sdk.R.anim.slide_down_from_top));
@@ -253,13 +255,13 @@ public void onPostCreated(FeedPost post) {
// Use our new slide up and fade animation
android.view.animation.Animation slideUpFade = android.view.animation.AnimationUtils.loadAnimation(
FeedExampleActivity.this, com.fastcomments.sdk.R.anim.slide_up_and_fade);
-
+
slideUpFade.setAnimationListener(new android.view.animation.Animation.AnimationListener() {
@Override
public void onAnimationStart(android.view.animation.Animation animation) {
// No need to change visibility yet
}
-
+
@Override
public void onAnimationEnd(android.view.animation.Animation animation) {
// Make form completely gone and unclickable
@@ -267,27 +269,28 @@ public void onAnimationEnd(android.view.animation.Animation animation) {
postCreateView.setVisibility(View.GONE);
postCreateView.setClickable(false);
postCreateView.setEnabled(false);
-
+
// Show FAB button
createPostFab.setVisibility(View.VISIBLE);
-
+
// Refresh the feed to show the new post
feedView.refresh();
}
-
+
@Override
public void onAnimationRepeat(android.view.animation.Animation animation) {}
});
-
+
postCreateView.startAnimation(slideUpFade);
-
+
// Refresh the feed to show the new post
feedView.refresh();
}
@Override
public void onPostCreateError(String errorMessage) {
- Toast.makeText(FeedExampleActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ Toast.makeText(FeedExampleActivity.this, errorMessage, Toast.LENGTH_SHORT)
+ .show();
}
@Override
@@ -295,13 +298,13 @@ public void onPostCreateCancelled() {
// Use our new slide up and fade animation
android.view.animation.Animation slideUpFade = android.view.animation.AnimationUtils.loadAnimation(
FeedExampleActivity.this, com.fastcomments.sdk.R.anim.slide_up_and_fade);
-
+
slideUpFade.setAnimationListener(new android.view.animation.Animation.AnimationListener() {
@Override
public void onAnimationStart(android.view.animation.Animation animation) {
// No need to change visibility yet
}
-
+
@Override
public void onAnimationEnd(android.view.animation.Animation animation) {
// Make form completely gone and unclickable
@@ -309,15 +312,15 @@ public void onAnimationEnd(android.view.animation.Animation animation) {
postCreateView.setVisibility(View.GONE);
postCreateView.setClickable(false);
postCreateView.setEnabled(false);
-
+
// Show FAB button
createPostFab.setVisibility(View.VISIBLE);
}
-
+
@Override
public void onAnimationRepeat(android.view.animation.Animation animation) {}
});
-
+
postCreateView.startAnimation(slideUpFade);
}
@@ -328,7 +331,7 @@ public void onImagePickerRequested() {
}
});
}
-
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -341,4 +344,4 @@ protected void onDestroy() {
feedView.cleanup();
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/FeedExampleCustomButtonsActivity.java b/app/src/main/java/com/fastcomments/FeedExampleCustomButtonsActivity.java
index e38214e..9bb923c 100644
--- a/app/src/main/java/com/fastcomments/FeedExampleCustomButtonsActivity.java
+++ b/app/src/main/java/com/fastcomments/FeedExampleCustomButtonsActivity.java
@@ -3,13 +3,11 @@
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
-
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
-
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.core.sso.FastCommentsSSO;
import com.fastcomments.core.sso.SimpleSSOUserData;
@@ -22,7 +20,6 @@
import com.fastcomments.sdk.UserClickSource;
import com.fastcomments.sdk.examples.GifPickerFeedToolbarButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
-
import java.util.List;
/**
@@ -105,7 +102,8 @@ public void onFeedLoaded(List posts) {
@Override
public void onFeedError(String errorMessage) {
// Error loading feed
- Toast.makeText(FeedExampleCustomButtonsActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ Toast.makeText(FeedExampleCustomButtonsActivity.this, errorMessage, Toast.LENGTH_SHORT)
+ .show();
}
@Override
@@ -158,9 +156,11 @@ private void setupUserClickListener() {
userClickListener = (context, userInfo, source) -> {
String sourceText = source == UserClickSource.NAME ? "name" : "avatar";
String contextText = context.isComment() ? "comment" : "feed post";
- Toast.makeText(FeedExampleCustomButtonsActivity.this,
- "Clicked " + userInfo.getDisplayName() + "'s " + sourceText + " in " + contextText,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ FeedExampleCustomButtonsActivity.this,
+ "Clicked " + userInfo.getDisplayName() + "'s " + sourceText + " in " + contextText,
+ Toast.LENGTH_SHORT)
+ .show();
};
}
@@ -183,8 +183,7 @@ private void setupPostCreationView() {
// Create constraints for post creation view (full width at top, floating above content)
ConstraintLayout.LayoutParams postCreateParams = new ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.MATCH_PARENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT);
+ ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
postCreateView.setLayoutParams(postCreateParams);
postCreateView.setElevation(16f); // Elevate above other content
@@ -194,9 +193,12 @@ private void setupPostCreationView() {
// Update constraints to position the view at the top
ConstraintSet initialConstraintSet = new ConstraintSet();
initialConstraintSet.clone(parentLayout);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.TOP, parentLayout.getId(), ConstraintSet.TOP, 0);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, 0);
- initialConstraintSet.connect(postCreateView.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.TOP, parentLayout.getId(), ConstraintSet.TOP, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.START, parentLayout.getId(), ConstraintSet.START, 0);
+ initialConstraintSet.connect(
+ postCreateView.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 0);
initialConstraintSet.applyTo(parentLayout);
// Create FAB
@@ -207,8 +209,7 @@ private void setupPostCreationView() {
// Create params for FAB
ConstraintLayout.LayoutParams fabParams = new ConstraintLayout.LayoutParams(
- ConstraintLayout.LayoutParams.WRAP_CONTENT,
- ConstraintLayout.LayoutParams.WRAP_CONTENT);
+ ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT);
createPostFab.setLayoutParams(fabParams);
// Add FAB to parent
@@ -217,7 +218,8 @@ private void setupPostCreationView() {
// Update constraints to position the FAB at bottom-end
ConstraintSet fabConstraintSet = new ConstraintSet();
fabConstraintSet.clone(parentLayout);
- fabConstraintSet.connect(createPostFab.getId(), ConstraintSet.BOTTOM, parentLayout.getId(), ConstraintSet.BOTTOM, 32);
+ fabConstraintSet.connect(
+ createPostFab.getId(), ConstraintSet.BOTTOM, parentLayout.getId(), ConstraintSet.BOTTOM, 32);
fabConstraintSet.connect(createPostFab.getId(), ConstraintSet.END, parentLayout.getId(), ConstraintSet.END, 32);
fabConstraintSet.applyTo(parentLayout);
@@ -273,7 +275,8 @@ public void onAnimationRepeat(android.view.animation.Animation animation) {}
@Override
public void onPostCreateError(String errorMessage) {
- Toast.makeText(FeedExampleCustomButtonsActivity.this, errorMessage, Toast.LENGTH_SHORT).show();
+ Toast.makeText(FeedExampleCustomButtonsActivity.this, errorMessage, Toast.LENGTH_SHORT)
+ .show();
}
@Override
@@ -323,4 +326,4 @@ protected void onDestroy() {
feedView.cleanup();
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/LiveChatExampleActivity.kt b/app/src/main/java/com/fastcomments/LiveChatExampleActivity.kt
index 451da3a..f94b38c 100644
--- a/app/src/main/java/com/fastcomments/LiveChatExampleActivity.kt
+++ b/app/src/main/java/com/fastcomments/LiveChatExampleActivity.kt
@@ -6,12 +6,12 @@ import androidx.appcompat.app.AppCompatActivity
import com.fastcomments.core.CommentWidgetConfig
import com.fastcomments.core.sso.FastCommentsSSO
import com.fastcomments.core.sso.SimpleSSOUserData
-import com.fastcomments.sdk.LiveChatView
import com.fastcomments.sdk.FastCommentsSDK
+import com.fastcomments.sdk.LiveChatView
import com.fastcomments.sdk.OnUserClickListener
+import com.fastcomments.sdk.UserClickContext
import com.fastcomments.sdk.UserClickSource
import com.fastcomments.sdk.UserInfo
-import com.fastcomments.sdk.UserClickContext
/**
* Example activity showing how to use the LiveChatView
@@ -38,8 +38,8 @@ class LiveChatExampleActivity : AppCompatActivity() {
val userData = SimpleSSOUserData(
"Example User",
"user@example.com",
- "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG"
- );
+ "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG",
+ )
val sso = FastCommentsSSO(userData)
config.sso = sso.prepareToSend()
sdk = FastCommentsSDK(config)
@@ -49,12 +49,14 @@ class LiveChatExampleActivity : AppCompatActivity() {
// Set the SDK instance for the view
liveChatView.setSDK(sdk)
-
+
liveChatView.setOnUserClickListener { context, userInfo, source ->
val sourceText = if (source == UserClickSource.NAME) "name" else "avatar"
- Toast.makeText(this@LiveChatExampleActivity,
- "Clicked ${userInfo.displayName}'s $sourceText in live chat",
- Toast.LENGTH_SHORT).show()
+ Toast.makeText(
+ this@LiveChatExampleActivity,
+ "Clicked ${userInfo.displayName}'s $sourceText in live chat",
+ Toast.LENGTH_SHORT,
+ ).show()
}
// Load the chat
@@ -66,7 +68,7 @@ class LiveChatExampleActivity : AppCompatActivity() {
sdk.refreshLiveEvents()
liveChatView.onResume()
}
-
+
override fun onPause() {
super.onPause()
liveChatView.onPause()
@@ -77,4 +79,4 @@ class LiveChatExampleActivity : AppCompatActivity() {
liveChatView.cleanup()
sdk.cleanup()
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/MainActivity.kt b/app/src/main/java/com/fastcomments/MainActivity.kt
index f65bcab..cf74549 100644
--- a/app/src/main/java/com/fastcomments/MainActivity.kt
+++ b/app/src/main/java/com/fastcomments/MainActivity.kt
@@ -9,14 +9,14 @@ import com.fastcomments.sdk.FastCommentsSDK
import com.fastcomments.sdk.FastCommentsTheme
import com.fastcomments.sdk.FastCommentsView
import com.fastcomments.sdk.OnUserClickListener
+import com.fastcomments.sdk.UserClickContext
import com.fastcomments.sdk.UserClickSource
import com.fastcomments.sdk.UserInfo
-import com.fastcomments.sdk.UserClickContext
class MainActivity : AppCompatActivity() {
-
+
private lateinit var commentsView: FastCommentsView
-
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -27,29 +27,27 @@ class MainActivity : AppCompatActivity() {
"https://example.com/page1", // Use your URL ID
"Example Page", // Page title
"example.com", // Domain
- "Example Demo" // Site name
+ "Example Demo", // Site name
)
-
+
// Optional configuration
// config.voteStyle = VoteStyle.Heart
// config.enableInfiniteScrolling = true
-
+
// Initialize the SDK
val sdk = FastCommentsSDK(config)
-
+
// Optional: Set a custom theme programmatically
val resources = resources
val theme = FastCommentsTheme.Builder()
.setPrimaryColor(resources.getColor(com.fastcomments.sdk.R.color.primary, null))
.setPrimaryLightColor(resources.getColor(com.fastcomments.sdk.R.color.primary_light, null))
.setPrimaryDarkColor(resources.getColor(com.fastcomments.sdk.R.color.primary_dark, null))
-
// Button theming
.setActionButtonColor(android.graphics.Color.parseColor("#FF1976D2"))
.setReplyButtonColor(android.graphics.Color.parseColor("#FF4CAF50"))
.setToggleRepliesButtonColor(android.graphics.Color.parseColor("#FFFF5722"))
.setLoadMoreButtonTextColor(android.graphics.Color.parseColor("#FF9C27B0"))
-
// Other UI colors
.setLinkColor(resources.getColor(com.fastcomments.sdk.R.color.fastcomments_link_color, null))
.setLinkColorPressed(resources.getColor(com.fastcomments.sdk.R.color.fastcomments_link_color_pressed, null))
@@ -60,26 +58,26 @@ class MainActivity : AppCompatActivity() {
.setDialogHeaderTextColor(resources.getColor(com.fastcomments.sdk.R.color.fastcomments_dialog_header_text_color, null))
.setOnlineIndicatorColor(resources.getColor(com.fastcomments.sdk.R.color.fastcomments_online_indicator_color, null))
.build()
-
+
// Apply the theme to the SDK
sdk.setTheme(theme)
// Find the comments view in the layout
commentsView = findViewById(R.id.commentsView)
-
+
// Set the SDK instance for the view
commentsView.setSDK(sdk)
-
+
commentsView.setOnUserClickListener { context, userInfo, source ->
val sourceText = if (source == UserClickSource.NAME) "name" else "avatar"
Toast.makeText(this@MainActivity, "Clicked ${userInfo.displayName}'s $sourceText", Toast.LENGTH_SHORT).show()
}
-
+
// Load comments
commentsView.load()
}
-
+
companion object {
private const val TAG = "FastCommentsExample"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/SecureSSOExampleActivity.kt b/app/src/main/java/com/fastcomments/SecureSSOExampleActivity.kt
index e935bc9..7f4c276 100644
--- a/app/src/main/java/com/fastcomments/SecureSSOExampleActivity.kt
+++ b/app/src/main/java/com/fastcomments/SecureSSOExampleActivity.kt
@@ -22,14 +22,14 @@ class SecureSSOExampleActivity : AppCompatActivity() {
"ssotest",
"https://fastcomments.com/demo",
"fastcomments.com",
- "Demo"
+ "Demo",
)
config.sso = getSSOTokenFromServer()
val sdk = FastCommentsSDK(config)
// Find the comments view in the layout
commentsView = findViewById(R.id.commentsView)
-
+
// Set the SDK instance for the view
commentsView.setSDK(sdk)
commentsView.load()
@@ -37,8 +37,8 @@ class SecureSSOExampleActivity : AppCompatActivity() {
private fun getSSOTokenFromServer(): String {
// DO THIS ON THE SERVER. THIS IS ONLY IN THE APP AS AN EXAMPLE!
- val userData = SecureSSOUserData("user-123", "user@example.com", "Example User", "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG");
- userData.displayName = "Fancy Name";
+ val userData = SecureSSOUserData("user-123", "user@example.com", "Example User", "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG")
+ userData.displayName = "Fancy Name"
val sso = FastCommentsSSO.createSecure("DEMO_API_SECRET", userData)
return sso.prepareToSend(); // send to client
}
diff --git a/app/src/main/java/com/fastcomments/SimpleSSOExampleActivity.kt b/app/src/main/java/com/fastcomments/SimpleSSOExampleActivity.kt
index 149f0ba..3ddec86 100644
--- a/app/src/main/java/com/fastcomments/SimpleSSOExampleActivity.kt
+++ b/app/src/main/java/com/fastcomments/SimpleSSOExampleActivity.kt
@@ -22,14 +22,14 @@ class SimpleSSOExampleActivity : AppCompatActivity() {
"ssotest",
"https://fastcomments.com/demo",
"fastcomments.com",
- "Demo"
+ "Demo",
)
// Optional configuration
// config.voteStyle = VoteStyle.Heart
// config.enableInfiniteScrolling = true
- val userData = SimpleSSOUserData("Example User", "user@example.com", "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG");
+ val userData = SimpleSSOUserData("Example User", "user@example.com", "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG")
val sso = FastCommentsSSO(userData)
config.sso = sso.prepareToSend()
val sdk = FastCommentsSDK(config)
@@ -45,4 +45,4 @@ class SimpleSSOExampleActivity : AppCompatActivity() {
companion object {
private const val TAG = "FastCommentsExample"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/ToolbarShowcaseActivity.kt b/app/src/main/java/com/fastcomments/ToolbarShowcaseActivity.kt
index a2a5c09..5c6eae0 100644
--- a/app/src/main/java/com/fastcomments/ToolbarShowcaseActivity.kt
+++ b/app/src/main/java/com/fastcomments/ToolbarShowcaseActivity.kt
@@ -36,7 +36,7 @@ class ToolbarShowcaseActivity : AppCompatActivity() {
"toolbar-showcase", // Different URL slug to avoid comment conflicts
"https://fastcomments.com/toolbar-demo",
"fastcomments.com",
- "Toolbar Demo"
+ "Toolbar Demo",
)
// Optional: Configure vote style
@@ -46,7 +46,7 @@ class ToolbarShowcaseActivity : AppCompatActivity() {
val userData = SimpleSSOUserData(
"Toolbar Demo User",
"toolbar-demo@example.com",
- "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG"
+ "https://staticm.fastcomments.com/1639362726066-DSC_0841.JPG",
)
val sso = FastCommentsSSO(userData)
config.sso = sso.prepareToSend()
@@ -118,10 +118,10 @@ class ToolbarShowcaseActivity : AppCompatActivity() {
// Or hide the toolbar entirely for this instance
inputView.setToolbarVisible(false)
}
- */
+ */
}
companion object {
private const val TAG = "ToolbarShowcase"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/ui/theme/Color.kt b/app/src/main/java/com/fastcomments/ui/theme/Color.kt
index e801c26..bbe8ddd 100644
--- a/app/src/main/java/com/fastcomments/ui/theme/Color.kt
+++ b/app/src/main/java/com/fastcomments/ui/theme/Color.kt
@@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
+val Pink40 = Color(0xFF7D5260)
diff --git a/app/src/main/java/com/fastcomments/ui/theme/Theme.kt b/app/src/main/java/com/fastcomments/ui/theme/Theme.kt
index 3709871..380bbec 100644
--- a/app/src/main/java/com/fastcomments/ui/theme/Theme.kt
+++ b/app/src/main/java/com/fastcomments/ui/theme/Theme.kt
@@ -14,13 +14,13 @@ import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
- tertiary = Pink80
+ tertiary = Pink80,
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
- tertiary = Pink40
+ tertiary = Pink40,
/* Other default colors to override
background = Color(0xFFFFFBFE),
@@ -30,7 +30,7 @@ private val LightColorScheme = lightColorScheme(
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
- */
+ */
)
@Composable
@@ -38,7 +38,7 @@ fun FastcommentsexamplesimpleTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
@@ -47,12 +47,13 @@ fun FastcommentsexamplesimpleTheme(
}
darkTheme -> DarkColorScheme
+
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
- content = content
+ content = content,
)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/fastcomments/ui/theme/Type.kt b/app/src/main/java/com/fastcomments/ui/theme/Type.kt
index 6a531df..8cdcec4 100644
--- a/app/src/main/java/com/fastcomments/ui/theme/Type.kt
+++ b/app/src/main/java/com/fastcomments/ui/theme/Type.kt
@@ -13,8 +13,8 @@ val Typography = Typography(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
- letterSpacing = 0.5.sp
- )
+ letterSpacing = 0.5.sp,
+ ),
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
@@ -30,5 +30,5 @@ val Typography = Typography(
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
- */
-)
\ No newline at end of file
+ */
+)
diff --git a/app/src/test/java/com/fastcomments/ExampleUnitTest.kt b/app/src/test/java/com/fastcomments/ExampleUnitTest.kt
index 2c155b7..de4fb94 100644
--- a/app/src/test/java/com/fastcomments/ExampleUnitTest.kt
+++ b/app/src/test/java/com/fastcomments/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package com.fastcomments
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*
@@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
-}
\ No newline at end of file
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 48367a4..f1ce22f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,35 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
-}
\ No newline at end of file
+ alias(libs.plugins.spotless)
+}
+
+spotless {
+ // Java sources: Palantir formatter (4-space indentation, conventional wrapping) - the
+ // closest mainstream auto-formatter to IntelliJ IDEA's Java defaults.
+ java {
+ target("app/src/**/*.java", "libraries/sdk/src/**/*.java")
+ targetExclude("**/build/**")
+ palantirJavaFormat()
+ removeUnusedImports()
+ trimTrailingWhitespace()
+ endWithNewline()
+ }
+ // Kotlin sources: ktlint configured (via .editorconfig) to the intellij_idea code style.
+ kotlin {
+ target("app/src/**/*.kt", "libraries/sdk/src/**/*.kt")
+ targetExclude("**/build/**")
+ ktlint()
+ trimTrailingWhitespace()
+ endWithNewline()
+ }
+ kotlinGradle {
+ target(
+ "build.gradle.kts",
+ "settings.gradle.kts",
+ "app/build.gradle.kts",
+ "libraries/sdk/build.gradle.kts",
+ )
+ ktlint()
+ }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0290c83..a8b68bd 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,6 +13,7 @@ material = "1.12.0"
fastcomments = "3.0.0"
constraintlayout = "2.1.4"
swiperefreshlayout = "1.1.0"
+spotless = "8.8.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -41,4 +42,5 @@ fastcommentsPubsub = { group = "com.fastcomments", name = "pubsub", version.ref
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
\ No newline at end of file
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
\ No newline at end of file
diff --git a/libraries/sdk/build.gradle.kts b/libraries/sdk/build.gradle.kts
index 4ad9df6..8407b8e 100644
--- a/libraries/sdk/build.gradle.kts
+++ b/libraries/sdk/build.gradle.kts
@@ -23,12 +23,17 @@ android {
}
}
+ lint {
+ // Snapshot of pre-existing issues so CI only fails on newly introduced ones.
+ baseline = file("lint-baseline.xml")
+ }
+
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
+ "proguard-rules.pro",
)
}
}
@@ -117,9 +122,9 @@ publishing {
maven {
name = "repsy"
val releasesRepoUrl = "https://repo.repsy.io/mvn/winrid/fastcomments"
- val snapshotsRepoUrl = "https://repo.repsy.io/mvn/winrid/fastcomments"
+ val snapshotsRepoUrl = "https://repo.repsy.io/mvn/winrid/fastcomments"
url = uri(if (releaseVersion.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
-
+
credentials {
username = findProperty("repsyUsername") as String? ?: System.getenv("REPSY_USERNAME")
password = findProperty("repsyPassword") as String? ?: System.getenv("REPSY_PASSWORD")
diff --git a/libraries/sdk/lint-baseline.xml b/libraries/sdk/lint-baseline.xml
new file mode 100644
index 0000000..c6ba3fc
--- /dev/null
+++ b/libraries/sdk/lint-baseline.xml
@@ -0,0 +1,26412 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/sdk/src/androidTest/java/com/fastcomments/sdk/ExampleInstrumentedTest.java b/libraries/sdk/src/androidTest/java/com/fastcomments/sdk/ExampleInstrumentedTest.java
index 8178e33..cf49ba7 100644
--- a/libraries/sdk/src/androidTest/java/com/fastcomments/sdk/ExampleInstrumentedTest.java
+++ b/libraries/sdk/src/androidTest/java/com/fastcomments/sdk/ExampleInstrumentedTest.java
@@ -1,15 +1,13 @@
package com.fastcomments.sdk;
-import android.content.Context;
+import static org.junit.Assert.*;
-import androidx.test.platform.app.InstrumentationRegistry;
+import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
-
/**
* Instrumented test, which will execute on an Android device.
*
@@ -23,4 +21,4 @@ public void useAppContext() {
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.fastcomments.client", appContext.getPackageName());
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/AddLinkDialog.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/AddLinkDialog.java
index 1e7f8a4..28c10b6 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/AddLinkDialog.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/AddLinkDialog.java
@@ -9,12 +9,9 @@
import android.webkit.URLUtil;
import android.widget.Button;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
-
-import com.google.android.material.textfield.TextInputEditText;
-
import com.fastcomments.model.FeedPostLink;
+import com.google.android.material.textfield.TextInputEditText;
/**
* Dialog for adding a link to a post
@@ -45,23 +42,23 @@ public AddLinkDialog(@NonNull Context context, OnLinkAddedListener listener) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+
setContentView(R.layout.dialog_add_link);
-
+
// Find views
linkUrlEditText = findViewById(R.id.linkUrlEditText);
linkTitleEditText = findViewById(R.id.linkTitleEditText);
linkDescriptionEditText = findViewById(R.id.linkDescriptionEditText);
linkErrorTextView = findViewById(R.id.linkErrorTextView);
-
+
// Find the buttons in the layout
- cancelButton = findViewById(android.R.id.button2); // Negative button
- addButton = findViewById(android.R.id.button1); // Positive button
-
+ cancelButton = findViewById(android.R.id.button2); // Negative button
+ addButton = findViewById(android.R.id.button1); // Positive button
+
// Set button click listeners
cancelButton.setOnClickListener(v -> dismiss());
addButton.setOnClickListener(v -> validateAndAddLink());
-
+
// Set dialog width
if (getWindow() != null) {
getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
@@ -74,57 +71,63 @@ protected void onCreate(Bundle savedInstanceState) {
private void validateAndAddLink() {
// Reset error state
linkErrorTextView.setVisibility(View.GONE);
-
+
// Get input values
- String url = linkUrlEditText.getText() != null ? linkUrlEditText.getText().toString().trim() : "";
- String title = linkTitleEditText.getText() != null ? linkTitleEditText.getText().toString().trim() : "";
- String description = linkDescriptionEditText.getText() != null ? linkDescriptionEditText.getText().toString().trim() : "";
-
+ String url = linkUrlEditText.getText() != null
+ ? linkUrlEditText.getText().toString().trim()
+ : "";
+ String title = linkTitleEditText.getText() != null
+ ? linkTitleEditText.getText().toString().trim()
+ : "";
+ String description = linkDescriptionEditText.getText() != null
+ ? linkDescriptionEditText.getText().toString().trim()
+ : "";
+
// Validate URL
if (url.isEmpty()) {
showError(getContext().getString(R.string.link_url_required));
return;
}
-
+
// Ensure URL is valid
if (!URLUtil.isValidUrl(url) && !url.startsWith("http://") && !url.startsWith("https://")) {
// Try adding https:// prefix if missing
url = "https://" + url;
-
+
// Check if it's valid now
if (!URLUtil.isValidUrl(url)) {
showError(getContext().getString(R.string.invalid_url));
return;
}
}
-
+
// Create the link object
FeedPostLink link = new FeedPostLink();
link.setUrl(url);
-
+
if (!title.isEmpty()) {
link.setTitle(title);
}
-
+
if (!description.isEmpty()) {
link.setDescription(description);
}
-
+
// Notify listener and close dialog
if (listener != null) {
listener.onLinkAdded(link);
}
-
+
dismiss();
}
-
+
/**
* Show an error message
- *
+ *
* @param errorMessage The error message to display
*/
private void showError(String errorMessage) {
linkErrorTextView.setText(errorMessage);
linkErrorTextView.setVisibility(View.VISIBLE);
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/AvatarFetcher.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/AvatarFetcher.java
index 18a15bc..93038b0 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/AvatarFetcher.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/AvatarFetcher.java
@@ -2,27 +2,28 @@
import android.content.Context;
import android.widget.ImageView;
-
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
-
import java.net.URI;
public class AvatarFetcher {
public static void fetchTransformInto(Context context, String avatarSrc, ImageView imageView) {
- Glide.with(context).load(avatarSrc)
+ Glide.with(context)
+ .load(avatarSrc)
.apply(RequestOptions.circleCropTransform())
.into(imageView);
}
public static void fetchTransformInto(Context context, URI uri, ImageView imageView) {
- Glide.with(context).load(uri)
+ Glide.with(context)
+ .load(uri)
.apply(RequestOptions.circleCropTransform())
.into(imageView);
}
public static void fetchTransformInto(Context context, int resourceId, ImageView imageView) {
- Glide.with(context).load(resourceId)
+ Glide.with(context)
+ .load(resourceId)
.apply(RequestOptions.circleCropTransform())
.into(imageView);
}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeAwardDialog.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeAwardDialog.java
index 569ec8d..6b26eab 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeAwardDialog.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeAwardDialog.java
@@ -9,7 +9,6 @@
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
-
import com.bumptech.glide.Glide;
import com.fastcomments.model.CommentUserBadgeInfo;
@@ -34,23 +33,23 @@ public void show(CommentUserBadgeInfo badge) {
dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
-
+
View view = LayoutInflater.from(context).inflate(R.layout.badge_award_dialog, null);
dialog.setContentView(view);
-
+
// Set up dialog views
TextView badgeText = view.findViewById(R.id.badgeDialogText);
ImageView badgeIcon = view.findViewById(R.id.badgeDialogIcon);
TextView badgeDescription = view.findViewById(R.id.badgeDialogDescription);
Button closeButton = view.findViewById(R.id.badgeDialogCloseButton);
-
+
// Set badge text (display label or fallback to description)
String displayText = badge.getDisplayLabel() != null ? badge.getDisplayLabel() : badge.getDescription();
badgeText.setText(displayText);
-
- // Set badge description
+
+ // Set badge description
badgeDescription.setText(badge.getDescription());
-
+
// Apply custom colors if provided
if (badge.getBackgroundColor() != null) {
try {
@@ -59,7 +58,7 @@ public void show(CommentUserBadgeInfo badge) {
// Use default background if color is invalid
}
}
-
+
if (badge.getTextColor() != null) {
try {
badgeText.setTextColor(Color.parseColor(badge.getTextColor()));
@@ -67,23 +66,21 @@ public void show(CommentUserBadgeInfo badge) {
// Use default text color if color is invalid
}
}
-
+
// Load badge icon if available
if (badge.getDisplaySrc() != null && !badge.getDisplaySrc().isEmpty()) {
badgeIcon.setVisibility(View.VISIBLE);
- Glide.with(context)
- .load(badge.getDisplaySrc())
- .into(badgeIcon);
+ Glide.with(context).load(badge.getDisplaySrc()).into(badgeIcon);
} else {
badgeIcon.setVisibility(View.GONE);
}
-
+
// Set close button action
closeButton.setOnClickListener(v -> dismiss());
-
+
dialog.show();
}
-
+
/**
* Dismiss the dialog
*/
@@ -92,4 +89,4 @@ public void dismiss() {
dialog.dismiss();
}
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeView.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeView.java
index f594b1a..be7e31a 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeView.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/BadgeView.java
@@ -7,7 +7,6 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import com.bumptech.glide.Glide;
import com.fastcomments.model.CommentUserBadgeInfo;
@@ -18,23 +17,23 @@ public class BadgeView {
/**
* Create a badge view based on badge information
- *
+ *
* @param context The context
* @param badge The badge information
* @return The badge view
*/
public static View createBadgeView(Context context, CommentUserBadgeInfo badge) {
View badgeView = LayoutInflater.from(context).inflate(R.layout.badge_item, null);
-
+
// Get references to the badge view components
ImageView badgeIcon = badgeView.findViewById(R.id.badgeIcon);
TextView badgeText = badgeView.findViewById(R.id.badgeText);
- LinearLayout badgeContainer = (LinearLayout)badgeView;
-
+ LinearLayout badgeContainer = (LinearLayout) badgeView;
+
// Set badge text
String displayText = badge.getDisplayLabel() != null ? badge.getDisplayLabel() : badge.getDescription();
badgeText.setText(displayText);
-
+
// Apply colors if provided
if (badge.getBackgroundColor() != null) {
try {
@@ -43,23 +42,23 @@ public static View createBadgeView(Context context, CommentUserBadgeInfo badge)
// Use default background if color is invalid
}
}
-
+
if (badge.getBorderColor() != null) {
try {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(1, 1, 1, 1);
- badgeContainer.setPadding(badgeContainer.getPaddingLeft() + 1,
+ badgeContainer.setPadding(
+ badgeContainer.getPaddingLeft() + 1,
badgeContainer.getPaddingTop() + 1,
- badgeContainer.getPaddingRight() + 1,
+ badgeContainer.getPaddingRight() + 1,
badgeContainer.getPaddingBottom() + 1);
badgeContainer.setBackgroundColor(Color.parseColor(badge.getBorderColor()));
} catch (IllegalArgumentException e) {
// Ignore invalid border color
}
}
-
+
if (badge.getTextColor() != null) {
try {
badgeText.setTextColor(Color.parseColor(badge.getTextColor()));
@@ -67,17 +66,15 @@ public static View createBadgeView(Context context, CommentUserBadgeInfo badge)
// Use default text color if color is invalid
}
}
-
+
// Load badge icon if available
if (badge.getDisplaySrc() != null && !badge.getDisplaySrc().isEmpty()) {
badgeIcon.setVisibility(View.VISIBLE);
- Glide.with(context)
- .load(badge.getDisplaySrc())
- .into(badgeIcon);
+ Glide.with(context).load(badge.getDisplaySrc()).into(badgeIcon);
} else {
badgeIcon.setVisibility(View.GONE);
}
-
+
return badgeView;
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/BottomCommentInputView.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/BottomCommentInputView.java
index 99e9f68..8c1de92 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/BottomCommentInputView.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/BottomCommentInputView.java
@@ -9,7 +9,6 @@
import android.text.style.BackgroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
-import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -24,13 +23,10 @@
import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
import com.fastcomments.model.APIError;
import com.fastcomments.model.UserSessionInfo;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -167,21 +163,33 @@ public void afterTextChanged(Editable s) {
switch (format) {
case "bold":
if (!hasMatchingStyleSpan(s, Typeface.BOLD, applyStart, applyEnd)) {
- s.setSpan(new StyleSpan(Typeface.BOLD), applyStart, applyEnd,
+ s.setSpan(
+ new StyleSpan(Typeface.BOLD),
+ applyStart,
+ applyEnd,
Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
break;
case "italic":
if (!hasMatchingStyleSpan(s, Typeface.ITALIC, applyStart, applyEnd)) {
- s.setSpan(new StyleSpan(Typeface.ITALIC), applyStart, applyEnd,
+ s.setSpan(
+ new StyleSpan(Typeface.ITALIC),
+ applyStart,
+ applyEnd,
Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
break;
case "code":
if (!hasMatchingSpan(s, TypefaceSpan.class, applyStart, applyEnd)) {
- s.setSpan(new TypefaceSpan("monospace"), applyStart, applyEnd,
+ s.setSpan(
+ new TypefaceSpan("monospace"),
+ applyStart,
+ applyEnd,
Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
- s.setSpan(new BackgroundColorSpan(0x20808080), applyStart, applyEnd,
+ s.setSpan(
+ new BackgroundColorSpan(0x20808080),
+ applyStart,
+ applyEnd,
Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
break;
@@ -198,7 +206,8 @@ public void afterTextChanged(Editable s) {
String plainText = commentInput.getText().toString().trim();
if (!plainText.isEmpty() && submitListener != null) {
String html = getText();
- String parentId = parentComment != null ? parentComment.getComment().getId() : null;
+ String parentId =
+ parentComment != null ? parentComment.getComment().getId() : null;
submitListener.onCommentSubmit(html, parentId);
}
});
@@ -290,7 +299,8 @@ public void setSubmitting(boolean submitting) {
sendButton.setVisibility(submitting ? View.GONE : View.VISIBLE);
sendProgress.setVisibility(submitting ? View.VISIBLE : View.GONE);
commentInput.setEnabled(!submitting);
- sendButton.setEnabled(!submitting && !commentInput.getText().toString().trim().isEmpty());
+ sendButton.setEnabled(
+ !submitting && !commentInput.getText().toString().trim().isEmpty());
}
public void showError(String error) {
@@ -367,11 +377,11 @@ public void setSDK(FastCommentsSDK sdk) {
private void setupMentions() {
// Initialize mention suggestions adapter
mentionsAdapter = new MentionSuggestionsAdapter(getContext(), mentionSuggestions);
-
+
// Create the popup window for mentions
createMentionPopup();
}
-
+
/**
* Create the popup window for mention suggestions
*/
@@ -383,18 +393,17 @@ private void createMentionPopup() {
popupListView.setDivider(getContext().getResources().getDrawable(android.R.color.darker_gray));
popupListView.setDividerHeight(1);
popupListView.setPadding(8, 8, 8, 8);
-
+
// Handle mention selection
popupListView.setOnItemClickListener((parent, view, position, id) -> {
if (position < mentionSuggestions.size()) {
selectUserMention(mentionSuggestions.get(position));
}
});
-
+
// Create the popup window
- mentionPopup = new PopupWindow(popupListView,
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.WRAP_CONTENT);
+ mentionPopup = new PopupWindow(
+ popupListView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
mentionPopup.setOutsideTouchable(true);
mentionPopup.setFocusable(false);
mentionPopup.setElevation(12);
@@ -417,7 +426,7 @@ private void handleMentionInput(CharSequence s, int start, int before, int count
cancelMention();
return;
}
-
+
// We're in the middle of typing a mention
if (start < mentionStartPosition) {
// Cursor moved before the @ symbol, cancel mention
@@ -426,12 +435,13 @@ private void handleMentionInput(CharSequence s, int start, int before, int count
// Extract the text between @ and cursor
int mentionLength = start + count - mentionStartPosition;
if (mentionLength > 0 && start + count <= s.length()) {
- String newMentionText = s.subSequence(mentionStartPosition + 1, start + count).toString();
-
+ String newMentionText = s.subSequence(mentionStartPosition + 1, start + count)
+ .toString();
+
// Check if mention text changed
if (!newMentionText.equals(currentMentionText)) {
currentMentionText = newMentionText;
-
+
// Cancel mention if space is added (end of mention)
if (currentMentionText.contains(" ")) {
cancelMention();
@@ -453,26 +463,26 @@ private void showMentionSuggestions() {
// Calculate height based on number of items (max 3 visible)
int maxItems = Math.min(mentionSuggestions.size(), 3);
float density = getContext().getResources().getDisplayMetrics().density;
- int itemHeight = (int)(50 * density); // 50dp per item
- int totalHeight = maxItems * itemHeight + (int)(16 * density); // Add padding
-
+ int itemHeight = (int) (50 * density); // 50dp per item
+ int totalHeight = maxItems * itemHeight + (int) (16 * density); // Add padding
+
// Set the popup height
mentionPopup.setHeight(totalHeight);
-
+
// Calculate position and size
int[] location = new int[2];
commentInput.getLocationOnScreen(location);
int inputWidth = commentInput.getWidth();
- int popupWidth = (int)(inputWidth * 0.9f); // 90% of input width
+ int popupWidth = (int) (inputWidth * 0.9f); // 90% of input width
mentionPopup.setWidth(popupWidth);
-
+
// Position popup above the entire bottom input view
int[] thisViewLocation = new int[2];
this.getLocationOnScreen(thisViewLocation);
-
- int xPos = location[0] + (int)(12 * density); // Small margin from left of input
- int yPos = thisViewLocation[1] - totalHeight - (int)(8 * density); // Above the entire view with margin
-
+
+ int xPos = location[0] + (int) (12 * density); // Small margin from left of input
+ int yPos = thisViewLocation[1] - totalHeight - (int) (8 * density); // Above the entire view with margin
+
if (!mentionPopup.isShowing()) {
mentionPopup.showAtLocation(commentInput, android.view.Gravity.NO_GRAVITY, xPos, yPos);
}
@@ -480,7 +490,7 @@ private void showMentionSuggestions() {
hideMentionSuggestions();
}
}
-
+
/**
* Hide the mention suggestions list
*/
@@ -489,7 +499,7 @@ private void hideMentionSuggestions() {
mentionPopup.dismiss();
}
}
-
+
/**
* Cancel the current mention being typed
*/
@@ -506,21 +516,21 @@ private void searchUsers(String searchTerm) {
if (sdk == null) {
return;
}
-
+
// Don't search if the term is empty
if (searchTerm == null || searchTerm.trim().isEmpty()) {
mentionSuggestions.clear();
mentionsAdapter.notifyDataSetChanged();
return;
}
-
+
// Don't search if already searching
if (isSearchingUsers) {
return;
}
isSearchingUsers = true;
-
+
sdk.searchUsers(searchTerm, new FCCallback>() {
@Override
public boolean onFailure(APIError error) {
@@ -530,13 +540,13 @@ public boolean onFailure(APIError error) {
// Clear any previous results
mentionSuggestions.clear();
mentionsAdapter.notifyDataSetChanged();
-
+
// Hide the list on error
hideMentionSuggestions();
});
return FCCallback.CONSUME;
}
-
+
@Override
public boolean onSuccess(List users) {
isSearchingUsers = false;
@@ -544,12 +554,12 @@ public boolean onSuccess(List users) {
post(() -> {
// Clear previous suggestions
mentionSuggestions.clear();
-
+
if (users != null && !users.isEmpty()) {
// Add new suggestions
mentionSuggestions.addAll(users);
mentionsAdapter.notifyDataSetChanged();
-
+
// Show the suggestions list
showMentionSuggestions();
} else {
@@ -589,17 +599,17 @@ private void selectUserMention(UserMention mention) {
// Reset the mention state
cancelMention();
}
-
+
/**
* Apply theme colors to the UI elements
*/
private void applyTheme() {
FastCommentsTheme theme = sdk != null ? sdk.getTheme() : null;
-
+
// Apply action button color to the send button
int actionButtonColor = ThemeColorResolver.getActionButtonColor(getContext(), theme);
sendButton.setImageTintList(ColorStateList.valueOf(actionButtonColor));
-
+
// Also apply to cancel reply button
if (cancelReplyButton != null) {
cancelReplyButton.setImageTintList(ColorStateList.valueOf(actionButtonColor));
@@ -732,23 +742,22 @@ private void addDefaultFormattingButtons() {
defaultToolbarButtons.clear();
// Bold button
- ImageButton boldBtn = addDefaultToolbarButton(R.drawable.ic_format_bold, R.string.format_bold,
- v -> toggleFormat("bold"));
+ ImageButton boldBtn =
+ addDefaultToolbarButton(R.drawable.ic_format_bold, R.string.format_bold, v -> toggleFormat("bold"));
defaultToolbarButtons.put("bold", boldBtn);
// Italic button
- ImageButton italicBtn = addDefaultToolbarButton(R.drawable.ic_format_italic, R.string.format_italic,
- v -> toggleFormat("italic"));
+ ImageButton italicBtn = addDefaultToolbarButton(
+ R.drawable.ic_format_italic, R.string.format_italic, v -> toggleFormat("italic"));
defaultToolbarButtons.put("italic", italicBtn);
// Link button
- ImageButton linkBtn = addDefaultToolbarButton(R.drawable.link_icon, R.string.add_link,
- v -> showLinkDialog());
+ ImageButton linkBtn = addDefaultToolbarButton(R.drawable.link_icon, R.string.add_link, v -> showLinkDialog());
defaultToolbarButtons.put("link", linkBtn);
// Code button
- ImageButton codeBtn = addDefaultToolbarButton(R.drawable.ic_code, R.string.format_code,
- v -> toggleFormat("code"));
+ ImageButton codeBtn =
+ addDefaultToolbarButton(R.drawable.ic_code, R.string.format_code, v -> toggleFormat("code"));
codeBtn.setOnLongClickListener(v -> {
toggleFormat("codeblock");
return true;
@@ -761,7 +770,8 @@ private void addDefaultFormattingButtons() {
*
* @return The created ImageButton, for tracking active states
*/
- private ImageButton addDefaultToolbarButton(int iconRes, int contentDescriptionRes, View.OnClickListener clickListener) {
+ private ImageButton addDefaultToolbarButton(
+ int iconRes, int contentDescriptionRes, View.OnClickListener clickListener) {
ImageButton button = new ImageButton(getContext());
button.setImageResource(iconRes);
button.setContentDescription(getContext().getString(contentDescriptionRes));
@@ -999,8 +1009,10 @@ public String getSelectedText() {
return "";
}
- return commentInput.getText().subSequence(
- Math.min(start, end), Math.max(start, end)).toString();
+ return commentInput
+ .getText()
+ .subSequence(Math.min(start, end), Math.max(start, end))
+ .toString();
}
/**
@@ -1150,10 +1162,9 @@ private void updateToolbarActiveStates() {
Editable editable = commentInput.getText();
int pos = Math.max(commentInput.getSelectionStart(), 0);
- boolean boldActive = RichTextHelper.isBoldActive(editable, pos)
- || activeFormatsForNextChar.contains("bold");
- boolean italicActive = RichTextHelper.isItalicActive(editable, pos)
- || activeFormatsForNextChar.contains("italic");
+ boolean boldActive = RichTextHelper.isBoldActive(editable, pos) || activeFormatsForNextChar.contains("bold");
+ boolean italicActive =
+ RichTextHelper.isItalicActive(editable, pos) || activeFormatsForNextChar.contains("italic");
boolean codeActive = RichTextHelper.isCodeActive(editable, pos)
|| activeFormatsForNextChar.contains("code")
|| activeFormatsForNextChar.contains("codeblock");
@@ -1171,8 +1182,7 @@ private void setButtonActiveState(ImageButton button, boolean active) {
button.setBackgroundColor(0x33000000);
} else {
TypedValue outValue = new TypedValue();
- getContext().getTheme().resolveAttribute(
- android.R.attr.selectableItemBackgroundBorderless, outValue, true);
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
button.setBackgroundResource(outValue.resourceId);
}
}
@@ -1192,4 +1202,4 @@ protected void onDetachedFromWindow() {
editableImageGetter.clearTargets();
}
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CallbackWrapper.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CallbackWrapper.java
index ce4be89..0cafe06 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CallbackWrapper.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CallbackWrapper.java
@@ -2,12 +2,10 @@
import android.os.Handler;
import android.util.Log;
-
import com.fastcomments.invoker.ApiCallback;
import com.fastcomments.invoker.ApiException;
import com.fastcomments.model.APIError;
import com.google.gson.Gson;
-
import java.util.List;
import java.util.Map;
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentEditDialog.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentEditDialog.java
index f495d9e..efd8aa1 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentEditDialog.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentEditDialog.java
@@ -2,8 +2,6 @@
import android.app.Dialog;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentFormView.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentFormView.java
index 3703e5d..4da80aa 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentFormView.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentFormView.java
@@ -7,7 +7,6 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
@@ -15,13 +14,10 @@
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
-
import com.fastcomments.model.APIError;
import com.fastcomments.model.PublicComment;
import com.fastcomments.model.UserSessionInfo;
-
import java.util.ArrayList;
import java.util.List;
@@ -40,7 +36,7 @@ public class CommentFormView extends LinearLayout {
private OnCancelReplyListener cancelListener;
private String parentId;
private RenderableComment parentComment;
-
+
// For @mentions functionality
private FastCommentsSDK sdk;
private MentionSuggestionsAdapter mentionsAdapter;
@@ -125,7 +121,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
cancelMention();
return;
}
-
+
// We're in the middle of typing a mention
if (start < mentionStartPosition) {
// Cursor moved before the @ symbol, cancel mention
@@ -134,12 +130,13 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
// Extract the text between @ and cursor
int mentionLength = start + count - mentionStartPosition;
if (mentionLength > 0 && start + count <= s.length()) {
- String newMentionText = s.subSequence(mentionStartPosition + 1, start + count).toString();
-
+ String newMentionText = s.subSequence(mentionStartPosition + 1, start + count)
+ .toString();
+
// Check if mention text changed
if (!newMentionText.equals(currentMentionText)) {
currentMentionText = newMentionText;
-
+
// Cancel mention if space is added (end of mention)
if (currentMentionText.contains(" ")) {
cancelMention();
@@ -162,8 +159,9 @@ public void afterTextChanged(Editable s) {
// Set up the submit button — serialize spans to HTML
submitButton.setOnClickListener(v -> {
String plainText = commentEditText.getText().toString().trim();
- String commentText = TextUtils.isEmpty(plainText) ? "" :
- RichTextHelper.toHtml(commentEditText.getText()).trim();
+ String commentText = TextUtils.isEmpty(plainText)
+ ? ""
+ : RichTextHelper.toHtml(commentEditText.getText()).trim();
if (TextUtils.isEmpty(plainText)) {
errorTextView.setText(R.string.empty_comment_error);
errorTextView.setVisibility(View.VISIBLE);
@@ -225,7 +223,7 @@ public void setCurrentUser(@NonNull UserSessionInfo userInfo) {
}
}
userNameTextView.setText(nameToShow);
-
+
if (userInfo.getAvatarSrc() != null) {
AvatarFetcher.fetchTransformInto(getContext(), userInfo.getAvatarSrc(), avatarImageView);
} else {
@@ -255,10 +253,10 @@ public void clearText() {
selectedMentions.clear();
}
}
-
+
/**
* Check if the comment text input is empty
- *
+ *
* @return true if the comment text is empty, false otherwise
*/
public boolean isTextEmpty() {
@@ -310,44 +308,44 @@ public void resetReplyState() {
commentEditText.setHint(R.string.comment_hint);
clearText(); // This will also clear selected mentions
}
-
+
/**
* Get the parent comment that's being replied to
- *
+ *
* @return The parent RenderableComment or null if not replying
*/
public RenderableComment getParentComment() {
return parentComment;
}
-
+
/**
* Set the SDK instance for API access
- *
+ *
* @param sdk FastCommentsSDK instance
*/
public void setSDK(FastCommentsSDK sdk) {
this.sdk = sdk;
applyTheme();
}
-
+
/**
* Apply theme colors to buttons and UI elements
*/
private void applyTheme() {
FastCommentsTheme theme = sdk != null ? sdk.getTheme() : null;
-
+
// Apply action button color to submit button
int actionButtonColor = ThemeColorResolver.getActionButtonColor(getContext(), theme);
if (submitButton != null) {
submitButton.setTextColor(actionButtonColor);
}
-
+
// Apply action button color to cancel button as well
if (cancelButton != null) {
cancelButton.setTextColor(actionButtonColor);
}
}
-
+
/**
* Show the mention suggestions list
*/
@@ -358,7 +356,7 @@ private void showMentionSuggestions() {
hideMentionSuggestions();
}
}
-
+
/**
* Hide the mention suggestions list
*/
@@ -367,7 +365,7 @@ private void hideMentionSuggestions() {
mentionSuggestionsList.setVisibility(View.GONE);
}
}
-
+
/**
* Cancel the current mention being typed
*/
@@ -376,31 +374,31 @@ private void cancelMention() {
currentMentionText = "";
hideMentionSuggestions();
}
-
+
/**
* Search for users by partial name
- *
+ *
* @param searchTerm The search term (text after @ symbol)
*/
private void searchUsers(String searchTerm) {
if (sdk == null) {
return;
}
-
+
// Don't search if the term is empty
if (searchTerm == null || searchTerm.trim().isEmpty()) {
mentionSuggestions.clear();
mentionsAdapter.notifyDataSetChanged();
return;
}
-
+
// Don't search if already searching
if (isSearchingUsers) {
return;
}
-
+
isSearchingUsers = true;
-
+
// Call the API to search for users
sdk.searchUsers(searchTerm, new FCCallback>() {
@Override
@@ -411,7 +409,7 @@ public boolean onFailure(APIError error) {
// Clear any previous results
mentionSuggestions.clear();
mentionsAdapter.notifyDataSetChanged();
-
+
// Hide the list on error
hideMentionSuggestions();
});
@@ -421,12 +419,12 @@ public boolean onFailure(APIError error) {
@Override
public boolean onSuccess(List users) {
isSearchingUsers = false;
-
+
// Use the main thread to update UI
post(() -> {
// Update the suggestions list
mentionSuggestions.clear();
-
+
// Only show the list if we have actual results
if (users != null && !users.isEmpty()) {
mentionSuggestions.addAll(users);
@@ -441,10 +439,10 @@ public boolean onSuccess(List users) {
}
});
}
-
+
/**
* Select a user mention from the suggestions list
- *
+ *
* @param mention The user mention to select
*/
private void selectUserMention(UserMention mention) {
@@ -471,13 +469,13 @@ private void selectUserMention(UserMention mention) {
// Reset the mention state
cancelMention();
}
-
+
/**
* Get the selected user mentions for the current comment
- *
+ *
* @return List of selected user mentions
*/
public List getSelectedMentions() {
return selectedMentions;
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentViewHolder.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentViewHolder.java
index 2db7875..b286f21 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentViewHolder.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentViewHolder.java
@@ -2,36 +2,22 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.Html;
import android.text.format.DateUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.RequestOptions;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
import com.fastcomments.core.VoteStyle;
import com.fastcomments.model.CommentUserBadgeInfo;
-
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
-import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -102,7 +88,7 @@ public CommentViewHolder(Context context, FastCommentsSDK sdk, @NonNull View ite
childPaginationControls = itemView.findViewById(R.id.childPaginationControls);
btnLoadMoreReplies = itemView.findViewById(R.id.btnLoadMoreReplies);
childPaginationProgressBar = itemView.findViewById(R.id.childPaginationProgressBar);
-
+
// Apply theme colors
applyTheme();
}
@@ -112,7 +98,7 @@ public CommentViewHolder(Context context, FastCommentsSDK sdk, @NonNull View ite
*/
private void applyTheme() {
FastCommentsTheme theme = sdk != null ? sdk.getTheme() : null;
-
+
// Apply action button colors to ImageButtons
int actionButtonColor = ThemeColorResolver.getActionButtonColor(context, theme);
if (upVoteButton != null) {
@@ -127,19 +113,19 @@ private void applyTheme() {
if (commentMenuButton != null) {
commentMenuButton.setImageTintList(ColorStateList.valueOf(actionButtonColor));
}
-
+
// Apply reply button color
int replyButtonColor = ThemeColorResolver.getReplyButtonColor(context, theme);
if (replyButton != null) {
replyButton.setTextColor(replyButtonColor);
}
-
+
// Apply toggle replies button color
int toggleRepliesButtonColor = ThemeColorResolver.getToggleRepliesButtonColor(context, theme);
if (toggleRepliesButton != null) {
toggleRepliesButton.setTextColor(toggleRepliesButtonColor);
}
-
+
// Apply load more button text color
int loadMoreButtonTextColor = ThemeColorResolver.getLoadMoreButtonTextColor(context, theme);
if (btnLoadMoreReplies != null) {
@@ -148,19 +134,22 @@ private void applyTheme() {
}
private boolean liveChatStyle = false;
-
+
/**
* Set whether this view is using live chat styling
* @param liveChatStyle True for live chat style
*/
public void setLiveChatStyle(boolean liveChatStyle) {
this.liveChatStyle = liveChatStyle;
-
+
// No need to modify the view since we're using different layouts
// Just store the flag for other behavior adjustments
}
-
- public void setComment(final RenderableComment comment, boolean disableUnverifiedLabel, final CommentsAdapter.OnToggleRepliesListener listener) {
+
+ public void setComment(
+ final RenderableComment comment,
+ boolean disableUnverifiedLabel,
+ final CommentsAdapter.OnToggleRepliesListener listener) {
Boolean isBlocked = comment.getComment().getIsBlocked();
boolean blocked = isBlocked != null && isBlocked;
@@ -264,7 +253,7 @@ public void setComment(final RenderableComment comment, boolean disableUnverifie
String htmlContent = comment.getComment().getCommentHTML();
contentTextView.setText(HtmlLinkHandler.parseHtml(context, htmlContent, contentTextView));
}
-
+
// No need for special handling for live chat - we're using a dedicated layout
// Indent child comments to reflect hierarchy
@@ -453,23 +442,20 @@ public void updateDateDisplay() {
if (useAbsoluteDates) {
// Use system's locale-aware date formatting
- Locale currentLocale = context.getResources().getConfiguration().getLocales().get(0);
- DateTimeFormatter formatter = DateTimeFormatter
- .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
+ Locale currentLocale =
+ context.getResources().getConfiguration().getLocales().get(0);
+ DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
.withLocale(currentLocale);
dateTextView.setText(date.format(formatter));
} else {
// Format as relative date: 2 minutes ago, 1 hour ago, etc.
CharSequence relativeTime = DateUtils.getRelativeTimeSpanString(
- date.toInstant().toEpochMilli(),
- System.currentTimeMillis(),
- DateUtils.MINUTE_IN_MILLIS
- );
+ date.toInstant().toEpochMilli(), System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS);
dateTextView.setText(relativeTime);
}
}
-
+
/**
* Update badges display for a comment
*
@@ -477,27 +463,27 @@ public void updateDateDisplay() {
*/
public void updateBadges(List badges) {
badgesContainer.removeAllViews();
-
+
if (badges == null || badges.isEmpty()) {
badgesContainer.setVisibility(View.GONE);
return;
}
-
+
badgesContainer.setVisibility(View.VISIBLE);
-
+
for (com.fastcomments.model.CommentUserBadgeInfo badge : badges) {
View badgeView = BadgeView.createBadgeView(context, badge);
badgesContainer.addView(badgeView);
}
}
-
+
/**
* Format a count number to an abbreviated format
* Examples:
* - 0-999: Shows as is (123)
* - 1,000-999,999: Shows as #K (1.2K, 45K, 999K)
* - 1,000,000+: Shows as #M (1.2M, 45M)
- *
+ *
* @param count The count to format
* @return Formatted string
*/
@@ -525,59 +511,59 @@ private String formatAbbreviatedCount(int count) {
}
}
}
-
+
/**
* Set the click listener for the comment menu button
- *
+ *
* @param commentMenuListener The listener to handle menu items
*/
public void setCommentMenuClickListener(final CommentsAdapter.OnCommentMenuItemListener commentMenuListener) {
commentMenuButton.setOnClickListener(v -> showCommentMenu(commentMenuListener));
}
-
+
/**
* Set the click listener for the user avatar
- *
+ *
* @param clickListener The click listener to set
*/
public void setAvatarClickListener(View.OnClickListener clickListener) {
avatarImageView.setOnClickListener(clickListener);
}
-
+
/**
* Set the click listener for the user name
- *
+ *
* @param clickListener The click listener to set
*/
public void setUserNameClickListener(View.OnClickListener clickListener) {
nameTextView.setOnClickListener(clickListener);
}
-
+
/**
* Show the comment menu
- *
+ *
* @param commentMenuListener The listener to handle menu items
*/
private void showCommentMenu(final CommentsAdapter.OnCommentMenuItemListener commentMenuListener) {
// Create popup menu
PopupMenu popupMenu = new PopupMenu(context, commentMenuButton);
popupMenu.inflate(R.menu.comment_menu);
-
+
// Get menu for adjustments
android.view.Menu menu = popupMenu.getMenu();
-
+
// Only show edit option for user's own comments
boolean isCurrentUserComment = false;
-
+
if (currentComment != null && sdk.getCurrentUser() != null) {
String commentUserId = currentComment.getComment().getUserId();
String currentUserId = sdk.getCurrentUser().getId();
-
+
if (commentUserId != null && currentUserId != null) {
isCurrentUserComment = commentUserId.equals(currentUserId);
}
}
-
+
// Show/hide edit and delete options based on ownership
menu.findItem(R.id.menu_edit_comment).setVisible(isCurrentUserComment);
menu.findItem(R.id.menu_delete_comment).setVisible(isCurrentUserComment);
@@ -636,7 +622,7 @@ private void showCommentMenu(final CommentsAdapter.OnCommentMenuItemListener com
return false;
});
-
+
// Show the popup menu
popupMenu.show();
}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsAdapter.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsAdapter.java
index a718b96..20cf4f2 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsAdapter.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsAdapter.java
@@ -1,25 +1,15 @@
package com.fastcomments.sdk;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-
import com.fastcomments.model.PublicComment;
-
-import java.time.LocalDate;
-import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.FormatStyle;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Locale;
public class CommentsAdapter extends RecyclerView.Adapter {
@@ -44,7 +34,7 @@ public CommentsAdapter(Context context, FastCommentsSDK sdk) {
this.sdk = sdk;
commentsTree.setAdapter(this);
}
-
+
public Context getContext() {
return context;
}
@@ -52,23 +42,23 @@ public Context getContext() {
public void setRequestingReplyListener(Callback listener) {
this.replyListener = listener;
}
-
+
public void setUpVoteListener(Callback listener) {
this.upVoteListener = listener;
}
-
+
public void setDownVoteListener(Callback listener) {
this.downVoteListener = listener;
}
-
+
public void setNewChildCommentsListener(Callback listener) {
this.newChildCommentsListener = listener;
}
-
+
public void setCommentMenuListener(OnCommentMenuItemListener listener) {
this.commentMenuListener = listener;
}
-
+
public void setUserClickListener(OnUserClickListener listener) {
this.userClickListener = listener;
}
@@ -81,7 +71,7 @@ public void setGetChildrenProducer(Producer {
commentsTree.setRepliesVisible(updatedComment, !updatedComment.isRepliesShown, (request, producer) -> {
// Create a new request with the button
@@ -158,30 +148,30 @@ private void bindCommentViewHolder(CommentViewHolder holder, int position) {
}
});
}
-
+
// Set up vote button click listeners
holder.setUpVoteClickListener(v -> {
if (upVoteListener != null) {
upVoteListener.call(comment);
}
});
-
+
holder.setDownVoteClickListener(v -> {
if (downVoteListener != null) {
downVoteListener.call(comment);
}
});
-
+
// Set up heart button click listener (uses same upvote handler)
holder.setHeartClickListener(v -> {
if (upVoteListener != null) {
upVoteListener.call(comment);
}
});
-
+
// Set up comment menu click listener
holder.setCommentMenuClickListener(commentMenuListener);
-
+
// Set up user click listeners
if (userClickListener != null) {
holder.setUserNameClickListener(v -> {
@@ -189,30 +179,25 @@ private void bindCommentViewHolder(CommentViewHolder holder, int position) {
UserClickContext context = UserClickContext.fromComment(comment.getComment());
userClickListener.onUserClicked(context, userInfo, UserClickSource.NAME);
});
-
+
holder.setAvatarClickListener(v -> {
UserInfo userInfo = UserInfo.fromComment(comment.getComment());
UserClickContext context = UserClickContext.fromComment(comment.getComment());
userClickListener.onUserClicked(context, userInfo, UserClickSource.AVATAR);
});
}
-
+
// Set up load more children click listener
holder.setLoadMoreChildrenClickListener(v -> {
if (getChildren != null && comment.isRepliesShown && comment.hasMoreChildren) {
// Mark as loading to update UI
comment.isLoadingChildren = true;
notifyItemChanged(position);
-
+
// Create a request for the next page of child comments
GetChildrenRequest paginationRequest = new GetChildrenRequest(
- comment.getComment().getId(),
- null,
- comment.childSkip,
- comment.childPageSize,
- true
- );
-
+ comment.getComment().getId(), null, comment.childSkip, comment.childPageSize, true);
+
getChildren.get(paginationRequest, childComments -> {
// Update has more flag based on response
getHandler().post(() -> {
@@ -223,10 +208,10 @@ private void bindCommentViewHolder(CommentViewHolder holder, int position) {
}
});
}
-
+
private void bindButtonViewHolder(ButtonViewHolder holder, int position) {
final RenderableButton button = (RenderableButton) commentsTree.visibleNodes.get(position);
-
+
if (button.getButtonType() == RenderableButton.TYPE_NEW_ROOT_COMMENTS) {
// New root comments button
holder.setButtonText(context.getString(R.string.show_new_comments, button.getCommentCount()));
@@ -247,7 +232,7 @@ private void bindButtonViewHolder(ButtonViewHolder holder, int position) {
});
}
}
-
+
private android.os.Handler getHandler() {
return new android.os.Handler(android.os.Looper.getMainLooper());
}
@@ -269,18 +254,22 @@ public int getPositionForComment(RenderableComment comment) {
public interface OnToggleRepliesListener {
void onToggle(RenderableComment comment, Button toggleButton);
}
-
+
/**
* Listener for comment menu items
*/
public interface OnCommentMenuItemListener {
void onEdit(String commentId, String commentText);
+
void onDelete(String commentId);
+
void onFlag(String commentId);
+
void onBlock(String commentId, String userName);
+
void onUnblock(String commentId, String userName);
}
-
+
/**
* ViewHolder for button items that prompt the user to load new comments
*/
@@ -288,7 +277,7 @@ static class ButtonViewHolder extends RecyclerView.ViewHolder {
private final Button button;
private final FastCommentsSDK sdk;
private final Context context;
-
+
public ButtonViewHolder(@NonNull View itemView, FastCommentsSDK sdk, Context context) {
super(itemView);
this.sdk = sdk;
@@ -296,42 +285,42 @@ public ButtonViewHolder(@NonNull View itemView, FastCommentsSDK sdk, Context con
button = itemView.findViewById(R.id.btnNewComments);
applyTheme();
}
-
+
/**
* Apply theme colors to the button
*/
private void applyTheme() {
FastCommentsTheme theme = sdk != null ? sdk.getTheme() : null;
-
+
// Apply load more button text color
int loadMoreButtonTextColor = ThemeColorResolver.getLoadMoreButtonTextColor(context, theme);
if (button != null) {
button.setTextColor(loadMoreButtonTextColor);
}
}
-
+
public void setButtonText(String text) {
button.setText(text);
}
-
+
public void setButtonClickListener(View.OnClickListener listener) {
button.setOnClickListener(listener);
}
}
-
+
/**
* Date separator view holder for the live chat view
*/
static class DateSeparatorViewHolder extends RecyclerView.ViewHolder {
private final TextView dateText;
-
+
public DateSeparatorViewHolder(@NonNull View itemView) {
super(itemView);
dateText = itemView.findViewById(R.id.dateSeparatorText);
}
-
+
public void setDate(RenderableNode.DateSeparator separator) {
dateText.setText(separator.getFormattedDate());
}
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsDialog.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsDialog.java
index bd0b42d..3a7430c 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsDialog.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsDialog.java
@@ -11,16 +11,12 @@
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
import com.fastcomments.model.APIError;
import com.fastcomments.model.FeedPost;
import com.fastcomments.model.FeedPostsStatsResponse;
-import com.fastcomments.model.PublicComment;
+import java.util.ArrayList;
+import java.util.List;
/**
* Dialog for displaying comments for a post
@@ -32,7 +28,7 @@ public class CommentsDialog extends Dialog {
private FastCommentsView commentsView;
private OnCommentAddedListener commentAddedListener;
private OnUserClickListener userClickListener;
-
+
/**
* Interface for notifying when a comment is added
*/
@@ -45,14 +41,14 @@ public CommentsDialog(@NonNull Context context, FeedPost post, FastCommentsFeedS
this.post = post;
this.feedSDK = feedSDK;
}
-
+
/**
* Set a listener to be notified when a comment is added
*/
public void setOnCommentAddedListener(OnCommentAddedListener listener) {
this.commentAddedListener = listener;
}
-
+
/**
* Get the current comment added listener
* @return The current listener
@@ -60,7 +56,7 @@ public void setOnCommentAddedListener(OnCommentAddedListener listener) {
public OnCommentAddedListener getOnCommentAddedListener() {
return commentAddedListener;
}
-
+
/**
* Set a listener to be notified when a user (name or avatar) is clicked
* @param listener The listener to set
@@ -72,7 +68,7 @@ public void setOnUserClickListener(OnUserClickListener listener) {
commentsView.setOnUserClickListener(listener);
}
}
-
+
/**
* Get the current user click listener
* @return The current listener
@@ -80,7 +76,7 @@ public void setOnUserClickListener(OnUserClickListener listener) {
public OnUserClickListener getOnUserClickListener() {
return userClickListener;
}
-
+
/**
* Get the post ID for this dialog
* @return The post ID
@@ -88,19 +84,18 @@ public OnUserClickListener getOnUserClickListener() {
public String getPostId() {
return post != null ? post.getId() : null;
}
-
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+
// Remove default dialog title
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.dialog_comments);
-
+
// Make dialog fill most of the screen
if (getWindow() != null) {
- getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
+ getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// Resize the dialog window when the soft keyboard appears so the bottom-anchored
// comment/reply input stays visible above the keyboard instead of behind it.
@@ -108,41 +103,38 @@ protected void onCreate(Bundle savedInstanceState) {
// set it explicitly here.
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
-
+
// Set up close button
ImageButton closeButton = findViewById(R.id.closeButton);
closeButton.setOnClickListener(v -> {
// Use the same logic as onBackPressed
onBackPressed();
});
-
+
// Set title if available
TextView titleTextView = findViewById(R.id.titleTextView);
if (post.getTitle() != null && !post.getTitle().isEmpty()) {
titleTextView.setText(post.getTitle());
}
-
+
// Create the comments SDK and view
FastCommentsSDK commentsSDK = feedSDK.createCommentsSDKForPost(post);
commentsView = new FastCommentsView(getContext(), commentsSDK);
-
+
// Apply theme colors to dialog header if theme is available
if (feedSDK.getTheme() != null) {
FrameLayout headerContainer = findViewById(R.id.headerContainer);
headerContainer.setBackgroundColor(
- ThemeColorResolver.getDialogHeaderBackgroundColor(getContext(), feedSDK.getTheme())
- );
-
- titleTextView.setTextColor(
- ThemeColorResolver.getDialogHeaderTextColor(getContext(), feedSDK.getTheme())
- );
+ ThemeColorResolver.getDialogHeaderBackgroundColor(getContext(), feedSDK.getTheme()));
+
+ titleTextView.setTextColor(ThemeColorResolver.getDialogHeaderTextColor(getContext(), feedSDK.getTheme()));
}
-
+
// Set user click listener if one was provided
if (userClickListener != null) {
commentsView.setOnUserClickListener(userClickListener);
}
-
+
// Set up comment form listener to know when a comment is posted
commentsView.setCommentPostListener((comment) -> {
// Update post stats to refresh comment count
@@ -155,16 +147,16 @@ public boolean onFailure(APIError error) {
// Silently fail - not critical
return CONSUME;
}
-
+
@Override
public boolean onSuccess(FeedPostsStatsResponse response) {
// Stats updated in the SDK's cache
-
+
// Notify the listener that a comment was added
if (commentAddedListener != null) {
commentAddedListener.onCommentAdded(post.getId());
}
-
+
return CONSUME;
}
});
@@ -173,20 +165,21 @@ public boolean onSuccess(FeedPostsStatsResponse response) {
commentAddedListener.onCommentAdded(post.getId());
}
});
-
+
// Add comments view to container
FrameLayout container = findViewById(R.id.commentsContainer);
- container.addView(commentsView, new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT));
-
+ container.addView(
+ commentsView,
+ new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
+
// Load comments
commentsView.load();
}
-
+
/**
* Check if the back press should be intercepted to handle comment form state
- *
+ *
* @return true if the back press should be intercepted, false otherwise
*/
private boolean shouldInterceptBackPress() {
@@ -194,26 +187,26 @@ private boolean shouldInterceptBackPress() {
if (commentsView == null) {
return false;
}
-
+
// Get the bottom comment input from CommentsView
BottomCommentInputView bottomInput = commentsView.getBottomCommentInput();
if (bottomInput == null) {
return false;
}
-
+
// Check if user has typed any text (either in reply or new comment)
boolean hasText = !bottomInput.isTextEmpty();
-
+
return hasText;
}
-
+
@Override
public void onBackPressed() {
if (shouldInterceptBackPress()) {
// Get the bottom input and check if it has a parent comment (reply) or text
BottomCommentInputView bottomInput = commentsView.getBottomCommentInput();
RenderableComment parentComment = bottomInput.getParentComment();
-
+
// Show confirmation dialog
String title, message;
if (parentComment != null) {
@@ -223,25 +216,25 @@ public void onBackPressed() {
title = getContext().getString(R.string.cancel_comment_title);
message = getContext().getString(R.string.cancel_comment_confirm);
}
-
+
new android.app.AlertDialog.Builder(getContext())
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(android.R.string.yes, (dialog, which) -> {
- // Proceed with cancellation
- bottomInput.clearReplyState();
- bottomInput.clearText();
-
- // Don't dismiss the dialog yet - let the user continue with comments
- })
- .setNegativeButton(android.R.string.no, null)
- .show();
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+ // Proceed with cancellation
+ bottomInput.clearReplyState();
+ bottomInput.clearText();
+
+ // Don't dismiss the dialog yet - let the user continue with comments
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
} else {
// No need to intercept, just dismiss the dialog
super.onBackPressed();
}
}
-
+
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -251,4 +244,4 @@ public void onDetachedFromWindow() {
commentsView = null;
}
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsTree.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsTree.java
index 6c3b5ba..db49ecf 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsTree.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CommentsTree.java
@@ -1,11 +1,8 @@
package com.fastcomments.sdk;
import android.content.Context;
-import android.util.Log;
-
import com.fastcomments.model.PublicComment;
import com.fastcomments.model.SortDirections;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -13,7 +10,6 @@
import java.util.Map;
import java.util.Set;
-
/**
* The way the RecyclerView works is that when it needs to load an element at an index it calls onBindViewHolder(index)
* To keep this as an n-time lookup without hashmaps it means we need to maintain an array of visible items.
@@ -49,15 +45,15 @@ public CommentsTree() {
public void setAdapter(CommentsAdapter adapter) {
this.adapter = adapter;
}
-
+
public CommentsAdapter getAdapter() {
return this.adapter;
}
-
+
public Context getContext() {
return adapter != null ? adapter.getContext() : null;
}
-
+
/**
* Set whether this tree should use live chat style rendering (with date separators)
* @param liveChatStyle true for live chat style
@@ -100,20 +96,20 @@ public void build(List comments) {
final RenderableComment renderableComment = new RenderableComment(comment);
addToMapAndRelated(renderableComment);
allComments.add(renderableComment);
-
+
// Check if we need a date separator
if (comment.getDate() != null) {
java.time.LocalDate commentDate = comment.getDate().toLocalDate();
-
+
if (currentDate == null || !currentDate.equals(commentDate)) {
// Add date separator for this new date
currentDate = commentDate;
visibleNodes.add(new RenderableNode.DateSeparator(currentDate));
}
}
-
+
visibleNodes.add(renderableComment);
-
+
// In live chat, we typically don't show children/replies
// But process them anyway in case this changes
if (comment.getChildren() != null) {
@@ -155,8 +151,8 @@ public void appendComments(List comments) {
}
// Notify the adapter of exactly what changed (the newly added comments) TODO doesn't work right
-// int itemCount = visibleNodes.size() - initialSize;
-// adapter.notifyItemRangeInserted(initialSize, itemCount);
+ // int itemCount = visibleNodes.size() - initialSize;
+ // adapter.notifyItemRangeInserted(initialSize, itemCount);
adapter.notifyDataSetChanged();
}
@@ -195,7 +191,11 @@ public void addForParent(String parentId, List comments) {
}
}
- private void handleChildren(List allComments, List visibleNodes, List comments, boolean visible) {
+ private void handleChildren(
+ List allComments,
+ List visibleNodes,
+ List comments,
+ boolean visible) {
for (PublicComment child : comments) {
final RenderableComment childRenderable = new RenderableComment(child);
addToMapAndRelated(childRenderable);
@@ -235,7 +235,8 @@ public void removeChildren(List publicComments, List ind
final RenderableComment childRenderable = commentsById.get(child.getId());
// see explanation at top of class
allComments.remove(childRenderable);
- int index = visibleNodes.indexOf(childRenderable); // TODO optimize away lookup if parent does not have children visible
+ int index = visibleNodes.indexOf(
+ childRenderable); // TODO optimize away lookup if parent does not have children visible
if (index >= 0) {
indexesRemoved.add(index);
visibleNodes.remove(index);
@@ -246,7 +247,10 @@ public void removeChildren(List publicComments, List ind
}
}
- public void setRepliesVisible(RenderableComment renderableComment, boolean areRepliesVisible, Producer> getChildren) {
+ public void setRepliesVisible(
+ RenderableComment renderableComment,
+ boolean areRepliesVisible,
+ Producer> getChildren) {
final boolean wasRepliesVisible = renderableComment.isRepliesShown;
if (wasRepliesVisible == areRepliesVisible) {
return;
@@ -274,8 +278,7 @@ public void setRepliesVisible(RenderableComment renderableComment, boolean areRe
RenderableButton newRepliesButton = new RenderableButton(
RenderableButton.TYPE_NEW_CHILD_COMMENTS,
renderableComment.getNewChildCommentsCount(),
- renderableComment.getComment().getId()
- );
+ renderableComment.getComment().getId());
// Add the button after the last child and track it
String parentId = renderableComment.getComment().getId();
@@ -294,12 +297,7 @@ public void setRepliesVisible(RenderableComment renderableComment, boolean areRe
// Create GetChildrenRequest with the comment ID and the toggle button
// Note: We can't directly get the button here, so we'll handle button reference via the adapter
GetChildrenRequest request = new GetChildrenRequest(
- renderableComment.getComment().getId(),
- null,
- 0,
- renderableComment.childPageSize,
- false
- );
+ renderableComment.getComment().getId(), null, 0, renderableComment.childPageSize, false);
final String parentId = renderableComment.getComment().getId();
getChildren.get(request, (asyncFetchedChildren) -> {
@@ -309,8 +307,9 @@ public void setRepliesVisible(RenderableComment renderableComment, boolean areRe
// Set hasMoreChildren based on child count vs. loaded children count
if (renderableComment.getComment().getChildCount() != null) {
int totalChildCount = renderableComment.getComment().getChildCount();
- int loadedChildCount = renderableComment.getComment().getChildren() != null ?
- renderableComment.getComment().getChildren().size() : 0;
+ int loadedChildCount = renderableComment.getComment().getChildren() != null
+ ? renderableComment.getComment().getChildren().size()
+ : 0;
renderableComment.hasMoreChildren = (loadedChildCount < totalChildCount);
}
@@ -323,8 +322,7 @@ public void setRepliesVisible(RenderableComment renderableComment, boolean areRe
RenderableButton newRepliesButton = new RenderableButton(
RenderableButton.TYPE_NEW_CHILD_COMMENTS,
renderableComment.getNewChildCommentsCount(),
- parentId
- );
+ parentId);
// Add the button after the last child and track it
visibleNodes.add(lastChildIndex + 1, newRepliesButton);
@@ -386,8 +384,8 @@ private int findLastChildIndex(RenderableComment parentComment) {
lastChildIndex = i;
} else if (node instanceof RenderableButton) {
RenderableButton button = (RenderableButton) node;
- if (button.getButtonType() == RenderableButton.TYPE_NEW_CHILD_COMMENTS &&
- parentId.equals(button.getParentId())) {
+ if (button.getButtonType() == RenderableButton.TYPE_NEW_CHILD_COMMENTS
+ && parentId.equals(button.getParentId())) {
lastChildIndex = i;
} else {
break;
@@ -409,8 +407,9 @@ public void insertChildrenAfter(RenderableComment renderableComment, List= 0; i--) {
RenderableNode node = visibleNodes.get(i);
@@ -508,15 +507,19 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
break; // Exit after finding the most recent date separator
} else if (node instanceof RenderableComment) {
RenderableComment lastComment = (RenderableComment) node;
- if (lastComment.getComment().getDate() != null &&
- lastComment.getComment().getDate().toLocalDate().equals(commentDate)) {
+ if (lastComment.getComment().getDate() != null
+ && lastComment
+ .getComment()
+ .getDate()
+ .toLocalDate()
+ .equals(commentDate)) {
// The previous comment is from the same date
needDateSeparator = false;
break;
}
}
}
-
+
if (needDateSeparator) {
RenderableNode.DateSeparator separator = new RenderableNode.DateSeparator(commentDate);
visibleNodes.add(separator);
@@ -524,11 +527,11 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
position++;
}
}
-
+
visibleNodes.add(renderableComment);
}
adapter.notifyItemInserted(position);
-
+
// Check for new user presence (optimized for single comment)
checkAndRequestUserPresenceStatus(renderableComment);
} else {
@@ -537,10 +540,8 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
// If this is the first new comment, add a "New Comments" button at the top
if (newRootCommentsButton == null) {
- newRootCommentsButton = new RenderableButton(
- RenderableButton.TYPE_NEW_ROOT_COMMENTS,
- newRootComments.size()
- );
+ newRootCommentsButton =
+ new RenderableButton(RenderableButton.TYPE_NEW_ROOT_COMMENTS, newRootComments.size());
visibleNodes.add(0, newRootCommentsButton);
adapter.notifyItemInserted(0);
} else {
@@ -548,10 +549,8 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
int buttonIndex = visibleNodes.indexOf(newRootCommentsButton);
if (buttonIndex >= 0) {
// Replace with updated button
- newRootCommentsButton = new RenderableButton(
- RenderableButton.TYPE_NEW_ROOT_COMMENTS,
- newRootComments.size()
- );
+ newRootCommentsButton =
+ new RenderableButton(RenderableButton.TYPE_NEW_ROOT_COMMENTS, newRootComments.size());
visibleNodes.set(buttonIndex, newRootCommentsButton);
adapter.notifyItemChanged(buttonIndex);
}
@@ -594,7 +593,7 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
int insertionIndex = findLastChildIndex(parent) + 1;
visibleNodes.add(insertionIndex, renderableComment);
adapter.notifyItemInserted(insertionIndex);
-
+
// Check for new user presence (optimized for single comment)
checkAndRequestUserPresenceStatus(renderableComment);
} else if (parent.isRepliesShown) {
@@ -613,8 +612,7 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
RenderableButton updatedButton = new RenderableButton(
RenderableButton.TYPE_NEW_CHILD_COMMENTS,
parent.getNewChildCommentsCount(),
- parentId
- );
+ parentId);
visibleNodes.set(buttonIndex, updatedButton);
newChildCommentsButtons.put(parentId, updatedButton);
adapter.notifyItemChanged(buttonIndex);
@@ -625,8 +623,7 @@ public void addComment(PublicComment comment, boolean displayNow, SortDirections
RenderableButton newRepliesButton = new RenderableButton(
RenderableButton.TYPE_NEW_CHILD_COMMENTS,
parent.getNewChildCommentsCount(),
- parentId
- );
+ parentId);
visibleNodes.add(insertionIndex, newRepliesButton);
newChildCommentsButtons.put(parentId, newRepliesButton);
adapter.notifyItemInserted(insertionIndex);
@@ -659,7 +656,7 @@ public void showNewRootComments() {
// Add all buffered comments at the top of the list in chronological order (oldest first)
// Convert PublicComment list to RenderableComment list as we add them
List addedComments = new ArrayList<>();
-
+
for (int i = 0; i < newRootComments.size(); i++) {
PublicComment comment = newRootComments.get(i);
RenderableComment renderableComment = commentsById.get(comment.getId());
@@ -669,7 +666,7 @@ public void showNewRootComments() {
addedComments.add(comment);
}
}
-
+
// Check for new user presence (optimized for the specific comments)
if (!addedComments.isEmpty()) {
checkAndRequestUserPresenceStatuses(addedComments);
@@ -726,7 +723,7 @@ public void showNewChildComments(String parentId) {
// the starting position by subtracting the number of inserted items
int startPosition = insertionIndex - newChildComments.size();
adapter.notifyItemRangeInserted(startPosition, newChildComments.size());
-
+
// Check for new user presence (optimized for specific comments)
checkAndRequestUserPresenceStatuses(newChildComments);
}
@@ -811,26 +808,26 @@ public void updateUserPresence(String userId, boolean isOnline) {
notifyItemChanged(comment);
}
}
-
+
/**
* Check for newly visible comments and return any user IDs we need to fetch presence for
- *
+ *
* @return A set of user IDs needing presence status updates
*/
public Set checkForNewlyVisibleCommentUsers() {
Set userIdsToFetch = new HashSet<>();
-
+
// Check all visible comments
for (RenderableNode node : visibleNodes) {
if (node instanceof RenderableComment) {
RenderableComment comment = (RenderableComment) node;
-
+
// Check regular user ID
String userId = comment.getComment().getUserId();
if (userId != null && !userId.isEmpty() && !userPresenceCache.containsKey(userId)) {
userIdsToFetch.add(userId);
}
-
+
// Check anonymous user ID
String anonUserId = comment.getComment().getAnonUserId();
if (anonUserId != null && !anonUserId.isEmpty() && !userPresenceCache.containsKey(anonUserId)) {
@@ -838,10 +835,10 @@ public Set checkForNewlyVisibleCommentUsers() {
}
}
}
-
+
return userIdsToFetch;
}
-
+
/**
* Check for newly visible comments and request presence status updates if needed
*/
@@ -849,62 +846,62 @@ public void checkAndRequestUserPresenceStatuses() {
Set userIdsToFetch = checkForNewlyVisibleCommentUsers();
requestPresenceStatusesIfNeeded(userIdsToFetch);
}
-
+
/**
* Check for newly visible comment and request presence status updates if needed
- *
+ *
* @param comment The specific comment that became visible
*/
public void checkAndRequestUserPresenceStatus(RenderableComment comment) {
Set userIdsToFetch = new HashSet<>();
-
+
// Check regular user ID
String userId = comment.getComment().getUserId();
if (userId != null && !userId.isEmpty() && !userPresenceCache.containsKey(userId)) {
userIdsToFetch.add(userId);
}
-
+
// Check anonymous user ID
String anonUserId = comment.getComment().getAnonUserId();
if (anonUserId != null && !anonUserId.isEmpty() && !userPresenceCache.containsKey(anonUserId)) {
userIdsToFetch.add(anonUserId);
}
-
+
requestPresenceStatusesIfNeeded(userIdsToFetch);
}
-
+
/**
* Check for newly visible comments and request presence status updates if needed
- *
+ *
* @param comments The specific comments that became visible
*/
public void checkAndRequestUserPresenceStatuses(List comments) {
if (comments == null || comments.isEmpty()) {
return;
}
-
+
Set userIdsToFetch = new HashSet<>();
-
+
for (PublicComment comment : comments) {
// Check regular user ID
String userId = comment.getUserId();
if (userId != null && !userId.isEmpty() && !userPresenceCache.containsKey(userId)) {
userIdsToFetch.add(userId);
}
-
+
// Check anonymous user ID
String anonUserId = comment.getAnonUserId();
if (anonUserId != null && !anonUserId.isEmpty() && !userPresenceCache.containsKey(anonUserId)) {
userIdsToFetch.add(anonUserId);
}
}
-
+
requestPresenceStatusesIfNeeded(userIdsToFetch);
}
-
+
/**
* Request presence status updates for a set of user IDs
- *
+ *
* @param userIdsToFetch The set of user IDs to fetch status for
*/
private void requestPresenceStatusesIfNeeded(Set userIdsToFetch) {
@@ -917,22 +914,22 @@ private void requestPresenceStatusesIfNeeded(Set userIdsToFetch) {
}
userIdsCSV.append(userId);
}
-
+
// Request presence status updates
presenceStatusListener.onPresenceStatusNeeded(userIdsCSV.toString());
}
}
-
+
// Interface for requesting presence status updates
public interface PresenceStatusListener {
void onPresenceStatusNeeded(String userIdsCSV);
}
-
+
private PresenceStatusListener presenceStatusListener;
-
+
/**
* Set the listener for presence status update requests
- *
+ *
* @param listener The listener to set
*/
public void setPresenceStatusListener(PresenceStatusListener listener) {
@@ -960,19 +957,20 @@ public boolean removeComment(String commentId) {
// Remove this from the cached list of user's comments.
if (comment.getComment().getUserId() != null) {
- final List usersComments = commentsByUserId.get(comment.getComment().getUserId());
+ final List usersComments =
+ commentsByUserId.get(comment.getComment().getUserId());
if (usersComments != null) {
usersComments.remove(comment);
}
}
if (comment.getComment().getAnonUserId() != null) {
- final List usersComments = commentsByUserId.get(comment.getComment().getAnonUserId());
+ final List usersComments =
+ commentsByUserId.get(comment.getComment().getAnonUserId());
if (usersComments != null) {
usersComments.remove(comment);
}
}
-
// Handle parent's child count if this is a reply
final String parentId = comment.getComment().getParentId();
if (parentId != null) {
@@ -1010,7 +1008,8 @@ public boolean removeComment(String commentId) {
adapter.notifyItemRemoved(visibleIndex);
if (comment.isRepliesShown && comment.getComment().getChildren() != null) {
- final List indexesToRemove = new ArrayList<>(comment.getComment().getChildren().size());
+ final List indexesToRemove =
+ new ArrayList<>(comment.getComment().getChildren().size());
removeChildren(comment.getComment().getChildren(), indexesToRemove);
for (Integer i : indexesToRemove) {
adapter.notifyItemRemoved(i); // TODO worth to optimize into one notification?
@@ -1020,7 +1019,7 @@ public boolean removeComment(String commentId) {
return true;
}
-
+
/**
* Updates the isBlocked status of comments based on the commentStatuses map
* returned from the block/unblock API response, and notifies the adapter.
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomImageGetter.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomImageGetter.java
index 6349944..cf538f3 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomImageGetter.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomImageGetter.java
@@ -2,15 +2,12 @@
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.target.CustomTarget;
@@ -33,7 +30,8 @@ public Drawable getDrawable(String source) {
final URLDrawable urlDrawable = new URLDrawable();
urlDrawable.setBounds(0, 0, textView.getWidth(), 100);
- boolean isGif = source != null && (source.endsWith(".gif") || source.contains(".gif?") || source.contains("/giphy.gif"));
+ boolean isGif = source != null
+ && (source.endsWith(".gif") || source.contains(".gif?") || source.contains("/giphy.gif"));
if (isGif) {
loadGif(source, urlDrawable);
@@ -45,61 +43,54 @@ public Drawable getDrawable(String source) {
}
private void loadGif(String source, URLDrawable urlDrawable) {
- Glide.with(context)
- .asGif()
- .load(source)
- .into(new CustomTarget() {
+ Glide.with(context).asGif().load(source).into(new CustomTarget() {
+ @Override
+ public void onResourceReady(
+ @NonNull GifDrawable resource, @Nullable Transition super GifDrawable> transition) {
+ resource.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());
+ resource.setLoopCount(GifDrawable.LOOP_FOREVER);
+
+ resource.setCallback(new Drawable.Callback() {
@Override
- public void onResourceReady(@NonNull GifDrawable resource, @Nullable Transition super GifDrawable> transition) {
- resource.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());
- resource.setLoopCount(GifDrawable.LOOP_FOREVER);
-
- resource.setCallback(new Drawable.Callback() {
- @Override
- public void invalidateDrawable(@NonNull Drawable who) {
- textView.invalidate();
- }
-
- @Override
- public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
- textView.postDelayed(what, when - android.os.SystemClock.uptimeMillis());
- }
-
- @Override
- public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
- textView.removeCallbacks(what);
- }
- });
+ public void invalidateDrawable(@NonNull Drawable who) {
+ textView.invalidate();
+ }
- urlDrawable.setDrawable(resource);
- urlDrawable.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());
- resource.start();
- textView.setText(textView.getText());
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ textView.postDelayed(what, when - android.os.SystemClock.uptimeMillis());
}
@Override
- public void onLoadCleared(@Nullable Drawable placeholder) {
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ textView.removeCallbacks(what);
}
});
+
+ urlDrawable.setDrawable(resource);
+ urlDrawable.setBounds(0, 0, resource.getIntrinsicWidth(), resource.getIntrinsicHeight());
+ resource.start();
+ textView.setText(textView.getText());
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {}
+ });
}
private void loadBitmap(String source, URLDrawable urlDrawable) {
- Glide.with(context)
- .asBitmap()
- .load(source)
- .into(new CustomTarget() {
- @Override
- public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition super Bitmap> transition) {
- BitmapDrawable drawable = new BitmapDrawable(context.getResources(), resource);
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- urlDrawable.setDrawable(drawable);
- urlDrawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- textView.setText(textView.getText());
- }
-
- @Override
- public void onLoadCleared(@Nullable Drawable placeholder) {
- }
- });
+ Glide.with(context).asBitmap().load(source).into(new CustomTarget() {
+ @Override
+ public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition super Bitmap> transition) {
+ BitmapDrawable drawable = new BitmapDrawable(context.getResources(), resource);
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ urlDrawable.setDrawable(drawable);
+ urlDrawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ textView.setText(textView.getText());
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {}
+ });
}
}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomToolbarButton.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomToolbarButton.java
index 5bd1858..11afa48 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomToolbarButton.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/CustomToolbarButton.java
@@ -101,4 +101,4 @@ default void onAttached(BottomCommentInputView view, View buttonView) {
default void onDetached(BottomCommentInputView view, View buttonView) {
// Default implementation does nothing
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/DemoBannerHelper.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/DemoBannerHelper.java
index d2361db..2b2de4a 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/DemoBannerHelper.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/DemoBannerHelper.java
@@ -9,10 +9,10 @@
* Helper class for setting up demo banners across SDK views
*/
public class DemoBannerHelper {
-
+
/**
* Sets up the demo banner if tenant ID is "demo"
- *
+ *
* @param containerView The view containing the demo banner
* @param sdk The SDK instance to check tenant ID
*/
@@ -20,10 +20,10 @@ public static void setupDemoBanner(View containerView, FastCommentsSDK sdk) {
String tenantId = sdk != null ? sdk.getConfig().tenantId : null;
setupDemoBannerInternal(containerView, tenantId);
}
-
+
/**
* Sets up the demo banner for feed views with feed SDK
- *
+ *
* @param containerView The view containing the demo banner
* @param feedSdk The feed SDK instance to check tenant ID
*/
@@ -31,10 +31,10 @@ public static void setupDemoBanner(View containerView, FastCommentsFeedSDK feedS
String tenantId = feedSdk != null ? feedSdk.getConfig().tenantId : null;
setupDemoBannerInternal(containerView, tenantId);
}
-
+
/**
* Internal method to handle the common demo banner setup logic
- *
+ *
* @param containerView The view containing the demo banner
* @param tenantId The tenant ID to check
*/
@@ -44,7 +44,7 @@ private static void setupDemoBannerInternal(View containerView, String tenantId)
// Show banner only if tenant ID is "demo"
if ("demo".equals(tenantId)) {
demoBanner.setVisibility(View.VISIBLE);
-
+
// Set up click listener for "Create an account" link
TextView createAccountLink = demoBanner.findViewById(R.id.createAccountLink);
if (createAccountLink != null) {
@@ -58,4 +58,4 @@ private static void setupDemoBannerInternal(View containerView, String tenantId)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/FCCallback.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/FCCallback.java
index 567eb8d..d8b6ca7 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/FCCallback.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/FCCallback.java
@@ -14,7 +14,6 @@ public interface FCCallback {
/**
* @return true to stop the callback chain
*/
-
boolean onSuccess(ResponseType response);
default void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
diff --git a/libraries/sdk/src/main/java/com/fastcomments/sdk/FastCommentsFeedSDK.java b/libraries/sdk/src/main/java/com/fastcomments/sdk/FastCommentsFeedSDK.java
index e4f92a7..9945c15 100644
--- a/libraries/sdk/src/main/java/com/fastcomments/sdk/FastCommentsFeedSDK.java
+++ b/libraries/sdk/src/main/java/com/fastcomments/sdk/FastCommentsFeedSDK.java
@@ -2,10 +2,13 @@
import static com.fastcomments.model.LiveEventType.NEW_FEED_POST;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
+import android.provider.OpenableColumns;
import android.util.Log;
-
import com.fastcomments.api.PublicApi;
import com.fastcomments.core.CommentWidgetConfig;
import com.fastcomments.invoker.ApiCallback;
@@ -20,16 +23,20 @@
import com.fastcomments.model.FeedPostMediaItemAsset;
import com.fastcomments.model.FeedPostStats;
import com.fastcomments.model.FeedPostsStatsResponse;
-import com.fastcomments.model.MediaAsset;
-import com.fastcomments.model.SizePreset;
-import com.fastcomments.model.UploadImageResponse;
-import com.fastcomments.model.PublicFeedPostsResponse;
import com.fastcomments.model.LiveEvent;
import com.fastcomments.model.LiveEventType;
+import com.fastcomments.model.MediaAsset;
+import com.fastcomments.model.PublicFeedPostsResponse;
import com.fastcomments.model.ReactBodyParams;
import com.fastcomments.model.ReactFeedPostResponse;
+import com.fastcomments.model.SizePreset;
+import com.fastcomments.model.UploadImageResponse;
import com.fastcomments.model.UserSessionInfo;
-
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -42,18 +49,6 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
-import android.net.Uri;
-import android.content.Context;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-
-import android.provider.OpenableColumns;
-import android.database.Cursor;
-
/**
* SDK class for handling FastComments Feed functionality
*/
@@ -119,7 +114,7 @@ public FastCommentsFeedSDK(CommentWidgetConfig config) {
public CommentWidgetConfig getConfig() {
return config;
}
-
+
/**
* Get the current theme
*
@@ -128,7 +123,7 @@ public CommentWidgetConfig getConfig() {
public FastCommentsTheme getTheme() {
return theme;
}
-
+
/**
* Set a TagSupplier to provide tags for filtering feed posts.
* The tags returned by the supplier will be used when fetching posts from the API.
@@ -138,7 +133,7 @@ public FastCommentsTheme getTheme() {
public void setTagSupplier(TagSupplier tagSupplier) {
this.tagSupplier = tagSupplier;
}
-
+
/**
* Get the current TagSupplier
*
@@ -425,7 +420,7 @@ public void restorePaginationState(FeedState state) {
this.myReacts.putAll(state.getMyReacts());
}
- // Restore like counts if available
+ // Restore like counts if available
if (state.getLikeCounts() != null) {
this.likeCounts.clear();
this.likeCounts.putAll(state.getLikeCounts());
@@ -439,7 +434,7 @@ public void restorePaginationState(FeedState state) {
*/
public void load(FCCallback callback) {
// Reset pagination for initial load
- lastPostId = null; // Reset the cursor for pagination
+ lastPostId = null; // Reset the cursor for pagination
// Reset any existing error message
blockingErrorMessage = null;
@@ -462,7 +457,7 @@ private void loadFeedPosts(FCCallback callback) {
if (tagSupplier != null) {
tags = tagSupplier.getTags(currentUser);
}
-
+
api.getFeedPostsPublic(config.tenantId)
.afterId(lastPostId)
.limit(pageSize)
@@ -471,18 +466,23 @@ private void loadFeedPosts(FCCallback callback) {
.tags(tags)
.executeAsync(new ApiCallback() {
@Override
- public void onFailure(ApiException e, int statusCode, Map> responseHeaders) {
+ public void onFailure(
+ ApiException e, int statusCode, Map> responseHeaders) {
APIError error = CallbackWrapper.createErrorFromException(e);
- if (error.getTranslatedError() != null && !error.getTranslatedError().isEmpty()) {
+ if (error.getTranslatedError() != null
+ && !error.getTranslatedError().isEmpty()) {
blockingErrorMessage = error.getTranslatedError();
- } else if (error.getReason() != null && !error.getReason().isEmpty()) {
+ } else if (error.getReason() != null
+ && !error.getReason().isEmpty()) {
blockingErrorMessage = "Feed could not load! Details: " + error.getReason();
}
// Log the error, particularly for JsonSyntaxException
- String errorMessage = "API Error: " + (e.getMessage() != null ? e.getMessage() : "Unknown error");
+ String errorMessage =
+ "API Error: " + (e.getMessage() != null ? e.getMessage() : "Unknown error");
if (e.getCause() != null) {
- errorMessage += " Cause: " + e.getCause().getClass().getSimpleName();
+ errorMessage +=
+ " Cause: " + e.getCause().getClass().getSimpleName();
if (e.getCause().getMessage() != null) {
errorMessage += " (" + e.getCause().getMessage() + ")";
}
@@ -493,99 +493,104 @@ public void onFailure(ApiException e, int statusCode, Map>
}
@Override
- public void onSuccess(PublicFeedPostsResponse response, int statusCode, Map> responseHeaders) {
- mainHandler.post(() -> {
- final PublicFeedPostsResponse publicResponse = response;
-
- boolean needsWebsocketReconnect = false;
+ public void onSuccess(
+ PublicFeedPostsResponse response,
+ int statusCode,
+ Map> responseHeaders) {
+ mainHandler.post(() -> {
+ final PublicFeedPostsResponse publicResponse = response;
- if (publicResponse.getUser() != null) {
- currentUser = publicResponse.getUser();
- }
+ boolean needsWebsocketReconnect = false;
- if (publicResponse.getTenantIdWS() != null) {
- tenantIdWS = publicResponse.getTenantIdWS();
- }
+ if (publicResponse.getUser() != null) {
+ currentUser = publicResponse.getUser();
+ }
- if (publicResponse.getUrlIdWS() != null) {
- urlIdWS = publicResponse.getUrlIdWS();
- }
+ if (publicResponse.getTenantIdWS() != null) {
+ tenantIdWS = publicResponse.getTenantIdWS();
+ }
- if (publicResponse.getUserIdWS() != null) {
- // Check if userIdWS has changed, which requires WebSocket reconnection
- if (userIdWS != null && !Objects.equals(publicResponse.getUserIdWS(), userIdWS)) {
- needsWebsocketReconnect = true;
- }
- userIdWS = publicResponse.getUserIdWS();
- }
+ if (publicResponse.getUrlIdWS() != null) {
+ urlIdWS = publicResponse.getUrlIdWS();
+ }
- // Subscribe to live events if we have all required parameters
- // or if we need to reconnect due to userIdWS change
- if ((tenantIdWS != null && urlIdWS != null && userIdWS != null) &&
- (liveEventSubscription == null || needsWebsocketReconnect)) {
- subscribeToLiveEvents();
+ if (publicResponse.getUserIdWS() != null) {
+ // Check if userIdWS has changed, which requires WebSocket reconnection
+ if (userIdWS != null && !Objects.equals(publicResponse.getUserIdWS(), userIdWS)) {
+ needsWebsocketReconnect = true;
}
+ userIdWS = publicResponse.getUserIdWS();
+ }
- final List posts = publicResponse.getFeedPosts();
+ // Subscribe to live events if we have all required parameters
+ // or if we need to reconnect due to userIdWS change
+ if ((tenantIdWS != null && urlIdWS != null && userIdWS != null)
+ && (liveEventSubscription == null || needsWebsocketReconnect)) {
+ subscribeToLiveEvents();
+ }
- // Process the myReacts from the response if available
- if (publicResponse.getMyReacts() != null) {
- // Only clear reactions if this is an initial load
- if (lastPostId == null) {
- myReacts.clear();
- }
- // Add all the myReacts for the posts
- myReacts.putAll(publicResponse.getMyReacts());
- }
+ final List posts = publicResponse.getFeedPosts();
- // Only clear the list if this is an initial load (no lastPostId)
- // This ensures we don't clear when paginating or loading more
+ // Process the myReacts from the response if available
+ if (publicResponse.getMyReacts() != null) {
+ // Only clear reactions if this is an initial load
if (lastPostId == null) {
- feedPosts.clear();
- postsById.clear();
- likeCounts.clear();
+ myReacts.clear();
}
+ // Add all the myReacts for the posts
+ myReacts.putAll(publicResponse.getMyReacts());
+ }
- if (!posts.isEmpty()) {
- // Add posts to list and maps
- for (FeedPost post : posts) {
- if (post.getId() != null) {
- // Store post by ID for quick lookup
- postsById.put(post.getId(), post);
-
- // Calculate initial like count from post's reacts
- if (post.getReacts() != null && post.getReacts().containsKey("l")) {
- likeCounts.put(post.getId(), post.getReacts().get("l").intValue());
- } else {
- likeCounts.put(post.getId(), 0);
- }
+ // Only clear the list if this is an initial load (no lastPostId)
+ // This ensures we don't clear when paginating or loading more
+ if (lastPostId == null) {
+ feedPosts.clear();
+ postsById.clear();
+ likeCounts.clear();
+ }
+
+ if (!posts.isEmpty()) {
+ // Add posts to list and maps
+ for (FeedPost post : posts) {
+ if (post.getId() != null) {
+ // Store post by ID for quick lookup
+ postsById.put(post.getId(), post);
+
+ // Calculate initial like count from post's reacts
+ if (post.getReacts() != null
+ && post.getReacts().containsKey("l")) {
+ likeCounts.put(
+ post.getId(),
+ post.getReacts()
+ .get("l")
+ .intValue());
+ } else {
+ likeCounts.put(post.getId(), 0);
}
}
+ }
- // Add to main post list
- feedPosts.addAll(posts);
+ // Add to main post list
+ feedPosts.addAll(posts);
- // Update lastPostId for pagination if we have posts
- FeedPost lastPost = posts.get(posts.size() - 1);
- lastPostId = lastPost.getId();
- }
+ // Update lastPostId for pagination if we have posts
+ FeedPost lastPost = posts.get(posts.size() - 1);
+ lastPostId = lastPost.getId();
+ }
- // Check if there are more posts to load
- // If we got posts back and size equals page size, assume more posts are available
- hasMore = !posts.isEmpty() && posts.size() >= pageSize;
+ // Check if there are more posts to load
+ // If we got posts back and size equals page size, assume more posts are available
+ hasMore = !posts.isEmpty() && posts.size() >= pageSize;
- callback.onSuccess(publicResponse);
- });
-
+ callback.onSuccess(publicResponse);
+ });
}
@Override
- public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
- }
+ public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
@Override
- public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
- }
+ public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
});
} catch (ApiException e) {
CallbackWrapper.handleAPIException(mainHandler, callback, e);
@@ -627,7 +632,7 @@ public boolean onSuccess(PublicFeedPostsResponse response) {
* @param callback Callback to receive the response
*/
public void refresh(FCCallback callback) {
- lastPostId = null; // Reset cursor-based pagination
+ lastPostId = null; // Reset cursor-based pagination
loadFeedPosts(callback);
}
@@ -716,7 +721,8 @@ public void likePost(String postId, FCCallback callback) {
.isUndo(isUndo)
.executeAsync(new ApiCallback() {
@Override
- public void onFailure(ApiException e, int statusCode, Map> responseHeaders) {
+ public void onFailure(
+ ApiException e, int statusCode, Map> responseHeaders) {
// API call failed - revert the optimistic update
if (isUndo) {
// Revert back to liked state
@@ -741,7 +747,10 @@ public void onFailure(ApiException e, int statusCode, Map>
}
@Override
- public void onSuccess(ReactFeedPostResponse result, int statusCode, Map> responseHeaders) {
+ public void onSuccess(
+ ReactFeedPostResponse result,
+ int statusCode,
+ Map> responseHeaders) {
// API call succeeded, our optimistic update was correct
mainHandler.post(() -> {
callback.onSuccess(updatedPost);
@@ -792,7 +801,7 @@ public void cleanup() {
}
// Clear listener references to prevent memory leaks
this.onPostDeletedListener = null;
-
+
// Clear collections to help GC
if (feedPosts != null) {
feedPosts.clear();
@@ -830,8 +839,9 @@ public FastCommentsSDK createCommentsSDKForPost(FeedPost post) {
config.pageTitle = post.getTitle();
} else if (post.getContentHTML() != null) {
// Use start of content as title if no title is available
- String contentText = android.text.Html.fromHtml(post.getContentHTML(),
- android.text.Html.FROM_HTML_MODE_COMPACT).toString();
+ String contentText = android.text.Html.fromHtml(
+ post.getContentHTML(), android.text.Html.FROM_HTML_MODE_COMPACT)
+ .toString();
// Limit to 100 characters
if (contentText.length() > 100) {
contentText = contentText.substring(0, 97) + "...";
@@ -846,16 +856,15 @@ public FastCommentsSDK createCommentsSDKForPost(FeedPost post) {
// Create a new FastCommentsSDK with this config
FastCommentsSDK commentsSDK = new FastCommentsSDK(config);
-
+
// Pass the theme from the feed SDK to the comments SDK
if (this.theme != null) {
commentsSDK.setTheme(this.theme);
}
-
+
return commentsSDK;
}
-
/**
* Uploads an image to the server with CrossPlatform preset
*
@@ -920,7 +929,8 @@ public void uploadImage(Context context, Uri imageUri, FCCallback() {
@Override
- public void onFailure(ApiException e, int statusCode, Map> responseHeaders) {
+ public void onFailure(
+ ApiException e, int statusCode, Map> responseHeaders) {
// Clean up the temp file
imageFile.delete();
@@ -929,7 +939,10 @@ public void onFailure(ApiException e, int statusCode, Map>
}
@Override
- public void onSuccess(UploadImageResponse result, int statusCode, Map> responseHeaders) {
+ public void onSuccess(
+ UploadImageResponse result,
+ int statusCode,
+ Map> responseHeaders) {
// Clean up the temp file
imageFile.delete();
@@ -946,20 +959,16 @@ public void onSuccess(UploadImageResponse result, int statusCode, Map assets = new ArrayList<>();
if (result.getMedia() != null) {
for (MediaAsset media : result.getMedia()) {
- assets.add(
- new FeedPostMediaItemAsset()
- .h(media.getH())
- .w(media.getW())
- .src(media.getSrc())
- );
+ assets.add(new FeedPostMediaItemAsset()
+ .h(media.getH())
+ .w(media.getW())
+ .src(media.getSrc()));
}
} else if (result.getUrl() != null) {
- assets.add(
- new FeedPostMediaItemAsset()
- .h(1000)
- .w(1000)
- .src(result.getUrl())
- );
+ assets.add(new FeedPostMediaItemAsset()
+ .h(1000)
+ .w(1000)
+ .src(result.getUrl()));
}
mediaItem.setSizes(assets);
@@ -1013,7 +1022,7 @@ public void uploadImages(Context context, List imageUris, FCCallback uploadedItems = new ArrayList<>();
final AtomicReference uploadError = new AtomicReference<>();
- int[] countRemaining = new int[]{imageUris.size()};
+ int[] countRemaining = new int[] {imageUris.size()};
for (Uri uri : imageUris) {
uploadImage(context, uri, new FCCallback() {
@@ -1088,7 +1097,7 @@ private String getFileExtension(String fileName) {
*/
private void copyUriToFile(Context context, Uri uri, File destFile) throws IOException {
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
- OutputStream outputStream = new FileOutputStream(destFile)) {
+ OutputStream outputStream = new FileOutputStream(destFile)) {
if (inputStream == null) {
throw new IOException("Failed to open input stream from URI");
}
@@ -1120,36 +1129,37 @@ public void createPost(CreateFeedPostParams params, FCCallback callbac
.sso(config.getSSOToken())
.executeAsync(new ApiCallback() {
@Override
- public void onFailure(ApiException e, int statusCode, Map> responseHeaders) {
+ public void onFailure(
+ ApiException e, int statusCode, Map> responseHeaders) {
APIError error = CallbackWrapper.createErrorFromException(e);
callback.onFailure(error);
}
@Override
- public void onSuccess(CreateFeedPostResponse response, int statusCode, Map> responseHeaders) {
+ public void onSuccess(
+ CreateFeedPostResponse response,
+ int statusCode,
+ Map> responseHeaders) {
mainHandler.post(() -> {
- CreateFeedPostResponse feedResponse = response;
- FeedPost createdPost = feedResponse.getFeedPost();
-
- // Add post to the local list at the beginning
- if (feedPosts.isEmpty()) {
- feedPosts.add(createdPost);
- } else {
- feedPosts.add(0, createdPost); // Add at the beginning
- }
+ CreateFeedPostResponse feedResponse = response;
+ FeedPost createdPost = feedResponse.getFeedPost();
+
+ // Add post to the local list at the beginning
+ if (feedPosts.isEmpty()) {
+ feedPosts.add(createdPost);
+ } else {
+ feedPosts.add(0, createdPost); // Add at the beginning
+ }
- callback.onSuccess(createdPost);
-
+ callback.onSuccess(createdPost);
});
}
@Override
- public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
- }
+ public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
@Override
- public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
- }
+ public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
});
} catch (ApiException e) {
CallbackWrapper.handleAPIException(mainHandler, callback, e);
@@ -1180,14 +1190,7 @@ private void subscribeToLiveEvents() {
// Subscribe to live events
liveEventSubscription = liveEventSubscriber.subscribeToChanges(
- config,
- tenantIdWS,
- config.urlId,
- urlIdWS,
- userIdWS,
- this::checkPostVisibility,
- this::handleLiveEvent
- );
+ config, tenantIdWS, config.urlId, urlIdWS, userIdWS, this::checkPostVisibility, this::handleLiveEvent);
}
/**
@@ -1201,7 +1204,8 @@ private void handleConnectionStatusChange(boolean isConnected, Long lastEventTim
/**
* Check if posts can be seen based on filtering/blocking logic
*/
- private void checkPostVisibility(List postIds, java.util.function.Consumer