From 611d0b2e4c884b6ed4cbc99036b42c4f0c843b9f Mon Sep 17 00:00:00 2001 From: enixma Date: Thu, 26 Sep 2024 16:55:55 +0700 Subject: [PATCH] chore: apply 4.0.0-beta21 source code --- .../AmityCommunitiesByCategoryPageSample.kt | 6 +- .../category/AmityAllCategoriesPageSample.kt | 6 +- ...AmityCommunityCategoriesComponentSample.kt | 12 +- ...tyRecommendedCommunitiesComponentSample.kt | 4 +- ...AmityTrendingCommunitiesComponentSample.kt | 12 +- app/.gitignore | 1 - app/build.gradle | 100 --- app/proguard-rules.pro | 21 - .../uikit/ExampleInstrumentedTest.kt | 22 - app/src/main/AndroidManifest.xml | 39 -- .../amity/socialcloud/uikit/MainActivity.kt | 12 - .../drawable-v24/ic_launcher_foreground.xml | 30 - .../res/drawable/ic_launcher_background.xml | 170 ----- .../main/res/layout/amity_activity_main.xml | 13 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3593 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5339 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2636 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 3388 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4926 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7472 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7909 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 11873 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 10652 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 16570 -> 0 bytes app/src/main/res/values/attrs.xml | 8 - app/src/main/res/values/colors.xml | 2 - app/src/main/res/values/strings.xml | 5 - app/src/main/res/values/styles.xml | 10 - app/src/main/res/xml/file_path.xml | 6 - .../socialcloud/uikit/ExampleUnitTest.kt | 16 - .../fragment/AmityChatHomePageFragment.kt | 2 +- ...tyChatRoomWithDefaultComposeBarFragment.kt | 128 +--- ...AmityChatRoomWithTextComposeBarFragment.kt | 95 --- .../fragment/AmityRecentChatFragment.kt | 2 +- common-compose/src/main/AndroidManifest.xml | 4 +- .../uikit/common/ui/base/AmityBaseElement.kt | 8 +- .../ui/elements/AmityExpandableTextView.kt | 4 +- .../common/ui/scope/AmityComposeScopeImpl.kt | 5 +- .../uikit/common/utils/AmityNumberExt.kt | 17 + common/src/main/AndroidManifest.xml | 14 + common/src/main/assets/config.json | 20 +- .../uikit/common/base/AmityPickerFragment.kt | 34 +- .../config/AmityUIKitDrawableResolver.kt | 2 - .../amity_ic_community_image_feed.xml | 12 + .../res/drawable/amity_ic_community_pin.xml | 15 +- .../amity_ic_community_video_feed.xml | 12 + main/.gitignore | 1 - main/build.gradle | 7 - main/consumer-rules.pro | 0 main/proguard-rules.pro | 21 - .../uikit/ExampleInstrumentedTest.kt | 22 - main/src/main/AndroidManifest.xml | 3 - .../socialcloud/uikit/ExampleUnitTest.kt | 16 - sample/src/main/AndroidManifest.xml | 6 +- settings.gradle | 2 - social-compose/src/main/AndroidManifest.xml | 3 +- .../comment/create/AmityCommentComposeBar.kt | 3 +- .../AmityCommentContentContainer.kt | 2 +- .../elements/AmityPendingPostActionRow.kt | 9 +- .../profile/AmityCommunityProfilePage.kt | 628 +++++++++++------- .../AmityCommunityProfilePageActivity.kt | 12 +- .../profile/AmityCommunityProfileViewModel.kt | 210 +++--- .../element/AmityCommunityEmptyView.kt | 119 +++- .../element/AmityCommunityImageFeedItem.kt | 121 ++++ .../element/AmityCommunityProfileTabRow.kt | 192 ++++++ .../element/AmityCommunityVideoFeedItem.kt | 187 ++++++ .../setting/AmityCommunitySettingPage.kt | 15 +- .../AmityCommunitySettingRadioGroup.kt | 4 +- .../setup/AmityCommunitySetupPage.kt | 37 +- .../elements/AmityMediaImageSelectionSheet.kt | 132 ++-- .../post/detail/AmityPostDetailPage.kt | 23 + .../post/detail/AmityPostVideoPlayerHelper.kt | 4 + .../components/AmityPostContentComponent.kt | 2 + .../elements/AmityPostEngagementView.kt | 9 +- .../elements/AmityPostMediaPreviewDialog.kt | 1 + .../AmityCreatePostMenuComponent.kt | 9 +- .../components/AmityGlobalFeedComponent.kt | 11 +- .../components/AmityNewsFeedComponent.kt | 5 +- .../story/create/AmityCreateStoryPage.kt | 22 +- .../res/drawable/amity_ic_photo_empty.xml | 9 + .../res/drawable/amity_ic_video_empty.xml | 9 + .../fragment/AmityBaseCreatePostFragment.kt | 121 +--- .../AmityLiveStreamPostCreatorFragment.kt | 56 +- .../view/AmityCommunityCreateBaseFragment.kt | 47 +- 86 files changed, 1507 insertions(+), 1452 deletions(-) delete mode 100644 app/.gitignore delete mode 100644 app/build.gradle delete mode 100644 app/proguard-rules.pro delete mode 100644 app/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt delete mode 100644 app/src/main/AndroidManifest.xml delete mode 100644 app/src/main/java/com/amity/socialcloud/uikit/MainActivity.kt delete mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 app/src/main/res/layout/amity_activity_main.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/values/attrs.xml delete mode 100644 app/src/main/res/values/colors.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/styles.xml delete mode 100644 app/src/main/res/xml/file_path.xml delete mode 100644 app/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt create mode 100644 common-compose/src/main/java/com/amity/socialcloud/uikit/common/utils/AmityNumberExt.kt create mode 100644 common/src/main/res/drawable/amity_ic_community_image_feed.xml create mode 100644 common/src/main/res/drawable/amity_ic_community_video_feed.xml delete mode 100644 main/.gitignore delete mode 100644 main/build.gradle delete mode 100644 main/consumer-rules.pro delete mode 100644 main/proguard-rules.pro delete mode 100644 main/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt delete mode 100644 main/src/main/AndroidManifest.xml delete mode 100644 main/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt create mode 100644 social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityImageFeedItem.kt create mode 100644 social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityProfileTabRow.kt create mode 100644 social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityVideoFeedItem.kt create mode 100644 social-compose/src/main/res/drawable/amity_ic_photo_empty.xml create mode 100644 social-compose/src/main/res/drawable/amity_ic_video_empty.xml diff --git a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/AmityCommunitiesByCategoryPageSample.kt b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/AmityCommunitiesByCategoryPageSample.kt index 36ec68c4..3bc7e482 100644 --- a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/AmityCommunitiesByCategoryPageSample.kt +++ b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/AmityCommunitiesByCategoryPageSample.kt @@ -6,10 +6,10 @@ import com.amity.socialcloud.uikit.community.compose.community.bycategory.AmityC class AmityCommunitiesByCategoryPageSample { /* begin_sample_code - gist_id: 2237c9d04341d2cd6606c7f2bc2ba43d + gist_id: 61a5adb590f9d3f74b301a77dacd717a filename: AmityCommunitiesByCategoryPageSample.kt asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ - description: Communities by category sample + description: Communities By Category Page sample */ @Composable fun composeCommunitiesByCatgoryPage( @@ -17,5 +17,5 @@ class AmityCommunitiesByCategoryPageSample { ) { AmityCommunitiesByCategoryPage(categoryId = categoryId) } - /* end_sample_code */ + } diff --git a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/category/AmityAllCategoriesPageSample.kt b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/category/AmityAllCategoriesPageSample.kt index 9ec6d01b..408efbf4 100644 --- a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/category/AmityAllCategoriesPageSample.kt +++ b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/community/category/AmityAllCategoriesPageSample.kt @@ -6,14 +6,14 @@ import com.amity.socialcloud.uikit.community.compose.community.category.AmityAll class AmityAllCategoriesPageSample { /* begin_sample_code - gist_id: bb4b996e326bc49039879816f10ceec9 + gist_id: 11d3b007c669aea5fb983d7114fccdcc filename: AmityAllCategoriesPageSample.kt asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ - description: All categories sample + description: All Categories Page sample */ @Composable fun composeAllCategoriesPage() { AmityAllCategoriesPage() } - /* end_sample_code */ + } diff --git a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityCommunityCategoriesComponentSample.kt b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityCommunityCategoriesComponentSample.kt index 52a3309a..d9f9c0ce 100644 --- a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityCommunityCategoriesComponentSample.kt +++ b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityCommunityCategoriesComponentSample.kt @@ -6,14 +6,14 @@ import com.amity.socialcloud.uikit.community.compose.community.category.componen class AmityCommunityCategoriesComponentSample { /* begin_sample_code - gist_id: 649cbf7f08f384340810e6d9c7b335e7 - filename: AmityCommunityCategoriesComponentSample.kt - asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ - description: Community categories sample - */ + gist_id: f7ee79d8ca7f0ffe812003beba93118a + filename: AmityCommunityCategoriesComponentSample.kt + asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ + description: Community categories sample + */ @Composable fun composeCommunityCategoriesComponent() { AmityCommunityCategoriesComponent() } - /* end_sample_code */ + } diff --git a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityRecommendedCommunitiesComponentSample.kt b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityRecommendedCommunitiesComponentSample.kt index 8ad0b1bd..31f87afc 100644 --- a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityRecommendedCommunitiesComponentSample.kt +++ b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityRecommendedCommunitiesComponentSample.kt @@ -6,7 +6,7 @@ import com.amity.socialcloud.uikit.community.compose.community.recommending.Amit class AmityRecommendedCommunitiesComponentSample { /* begin_sample_code - gist_id: 5933b4141a737e16df4ffa230400e6e5 + gist_id: cb88ed2a8915f3a3c1bfe01b0ebe997b filename: AmityRecommendedCommunitiesComponentSample.kt asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ description: Recommended communities sample @@ -15,5 +15,5 @@ class AmityRecommendedCommunitiesComponentSample { fun composeRecommendedCommunitiesComponent() { AmityRecommendedCommunitiesComponent() } - /* end_sample_code */ + } \ No newline at end of file diff --git a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityTrendingCommunitiesComponentSample.kt b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityTrendingCommunitiesComponentSample.kt index b1542c03..304b4465 100644 --- a/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityTrendingCommunitiesComponentSample.kt +++ b/amity-sample-code/src/main/java/com/amity/snipet/verifier/social/socialhome/explore/AmityTrendingCommunitiesComponentSample.kt @@ -6,14 +6,14 @@ import com.amity.socialcloud.uikit.community.compose.community.trending.AmityTre class AmityTrendingCommunitiesComponentSample { /* begin_sample_code - gist_id: cab31bb96f01e32ca26f4a288d1ba1b0 - filename: AmityTrendingCommunitiesComponentSample.kt - asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ - description: Trending communities sample - */ + gist_id: 27dddbd2046b26c50cbf6aebcd268203 + filename: AmityTrendingCommunitiesComponentSample.kt + asc_page: https://docs.amity.co/amity-uikit/uikit-v4-beta/ + description: Trending communities sample + */ @Composable fun composeTrendingCommunitiesComponent() { AmityTrendingCommunitiesComponent() } - /* end_sample_code */ + } \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 674ff605..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,100 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion rootProject.amityCompileSdkVersion - buildToolsVersion "34.0.0" - - defaultConfig { - applicationId "com.ekoapp.ekouisdk" - minSdkVersion rootProject.amityMinSdkVersion - targetSdkVersion rootProject.amityTargetSdkVersion - versionCode 1 - versionName "1.0" - multiDexEnabled true - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '17' - } - - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - - buildTypes { - debug { - minifyEnabled false - debuggable true - testCoverageEnabled true - } - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - lintOptions { - quiet true - xmlReport false - checkDependencies false - abortOnError false - checkAllWarnings true - warningsAsErrors true - - disable "GoogleAppIndexingWarning" - disable "NewerVersionAvailable" - disable "UnusedResources" - disable "LogNotTimber", "StringFormatInTimber", "ThrowableNotAtBeginning", "BinaryOperationInTimber", - "TimberArgCount", "TimberArgTypes", "TimberTagLength", "TimberExceptionLogging" - } - - dexOptions { - preDexLibraries false - maxProcessCount 4 - javaMaxHeapSize "2g" - } - - // Always show the result of every unit test, even if it passes. - testOptions { - unitTests.returnDefaultValues = true - unitTests { - includeAndroidResources = true - } - unitTests.all { - testLogging { - events 'passed', 'skipped', 'failed', 'standardOut', 'standardError' - } - } - - //execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - buildFeatures { - dataBinding = true - } - -} - -dependencies { - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - sharedSupportLibraries dependencies - - implementation project(path: ':common') - implementation project(path: ':social') - implementation project(path: ':chat') - - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt deleted file mode 100644 index 13091c15..00000000 --- a/app/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.amity.socialcloud.uikit - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.amity.socialcloud.uikit.test", appContext.packageName) - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index e4d9978f..00000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/amity/socialcloud/uikit/MainActivity.kt b/app/src/main/java/com/amity/socialcloud/uikit/MainActivity.kt deleted file mode 100644 index 63bdb0c3..00000000 --- a/app/src/main/java/com/amity/socialcloud/uikit/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.amity.socialcloud.uikit - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.amity_activity_main) - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d11..00000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9c..00000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/amity_activity_main.xml b/app/src/main/res/layout/amity_activity_main.xml deleted file mode 100644 index 8a6409a5..00000000 --- a/app/src/main/res/layout/amity_activity_main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cfe..00000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cfe..00000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a571e60098c92c2baca8a5df62f2929cbff01b52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3593 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 P00000NkvXXu0mjfP=x?Y diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 61da551c5594a1f9d26193983d2cd69189014603..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5339 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 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b216f2d313cc673d8b8c4da591c174ebed52795c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11873 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 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml deleted file mode 100644 index aa310513..00000000 --- a/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index 3ea04e70..00000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index bd5daa96..00000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - SonarUIKitAndroid - - Hello blank fragment - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 49f59f1e..00000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/xml/file_path.xml b/app/src/main/res/xml/file_path.xml deleted file mode 100644 index f52407ab..00000000 --- a/app/src/main/res/xml/file_path.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt b/app/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt deleted file mode 100644 index 806d7b6b..00000000 --- a/app/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.amity.socialcloud.uikit - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/chat/src/main/java/com/amity/socialcloud/uikit/chat/home/fragment/AmityChatHomePageFragment.kt b/chat/src/main/java/com/amity/socialcloud/uikit/chat/home/fragment/AmityChatHomePageFragment.kt index f1c53fb3..e3ba997d 100644 --- a/chat/src/main/java/com/amity/socialcloud/uikit/chat/home/fragment/AmityChatHomePageFragment.kt +++ b/chat/src/main/java/com/amity/socialcloud/uikit/chat/home/fragment/AmityChatHomePageFragment.kt @@ -18,7 +18,7 @@ import com.amity.socialcloud.uikit.common.common.showSnackBar import com.amity.socialcloud.uikit.common.contract.AmityPickMemberContract import com.ekoapp.rxlifecycle.extension.untilLifecycleEnd -class AmityChatHomePageFragment private constructor() : Fragment() { +class AmityChatHomePageFragment : Fragment() { private lateinit var mViewModel: AmityChatHomePageViewModel private lateinit var fragmentStateAdapter: AmityFragmentStateAdapter diff --git a/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithDefaultComposeBarFragment.kt b/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithDefaultComposeBarFragment.kt index 2c4ddc35..9a10824b 100644 --- a/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithDefaultComposeBarFragment.kt +++ b/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithDefaultComposeBarFragment.kt @@ -4,7 +4,6 @@ import android.Manifest import android.annotation.SuppressLint import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.net.Uri import android.os.Build @@ -15,6 +14,7 @@ import android.view.* import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity @@ -37,7 +37,6 @@ import com.amity.socialcloud.uikit.chat.messages.adapter.AmityMessagePagingAdapt import com.amity.socialcloud.uikit.chat.messages.viewModel.AmityChatRoomEssentialViewModel import com.amity.socialcloud.uikit.chat.messages.viewModel.AmityMessageListViewModel import com.amity.socialcloud.uikit.chat.util.MessageType -import com.amity.socialcloud.uikit.common.base.AmityImagePickerActivity import com.amity.socialcloud.uikit.common.base.AmityPickerFragment import com.amity.socialcloud.uikit.common.common.setShape import com.amity.socialcloud.uikit.common.common.showSnackBar @@ -50,9 +49,6 @@ import com.amity.socialcloud.uikit.common.utils.AmityAndroidUtil import com.amity.socialcloud.uikit.common.utils.AmityConstants import com.amity.socialcloud.uikit.common.utils.AmityRecyclerViewItemDecoration import com.google.android.material.snackbar.Snackbar -import com.zhihu.matisse.Matisse -import com.zhihu.matisse.MimeType -import com.zhihu.matisse.engine.impl.GlideEngine import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers @@ -77,8 +73,7 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), private var currentCount = 0 private var isImagePermissionGranted = false private var isReachBottom = true - private lateinit var imagePickerLauncher: ActivityResultLauncher - + private lateinit var imagePickerLauncher: ActivityResultLauncher private val requiredPermissions = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { arrayOf( @@ -91,12 +86,7 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE ) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - arrayOf( - Manifest.permission.RECORD_AUDIO, - Manifest.permission.READ_MEDIA_AUDIO - ) - } else { + } else { arrayOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_MEDIA_AUDIO @@ -114,22 +104,6 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), recordPermissionGranted = isGranted } - private val pickMultipleImagesPermission = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - val isGranted = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - it - } else { - true - } - if (isGranted) { - isImagePermissionGranted = true - pickMultipleImages() - } else { - isImagePermissionGranted = false - view?.showSnackBar("Permission denied", Snackbar.LENGTH_SHORT) - } - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) essentialViewModel = @@ -164,6 +138,7 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), registerImagePickerResult() // observeRefreshStatus() // observeConnectionStatus() + } private fun setupComposebar() { @@ -214,15 +189,23 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), */ private fun registerImagePickerResult() { - imagePickerLauncher = requireActivity().registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.let { - val uris = it.let(AmityImagePickerActivity::getUris)?.toList() - if (!uris.isNullOrEmpty()) { - uris.map(::addImageToList) - } - } + imagePickerLauncher = registerForActivityResult( + ActivityResultContracts.PickMultipleVisualMedia( + AmityConstants.MAX_SELECTION_COUNT + ) + ) { uris -> + // Callback is invoked after the user selects media items or closes the + // photo picker. + for (uri in uris) { + disposable.add(messageListViewModel.sendImageMessage(uri) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnComplete { + msgSent = true + }.doOnError { + msgSent = false + }.subscribe() + ) } } } @@ -533,36 +516,7 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), } private fun pickMultipleImages() { - if (isImagePermissionGranted) { - currentCount = 0 - if (currentCount == AmityConstants.MAX_SELECTION_COUNT) { - view?.showSnackBar(getString(com.amity.socialcloud.uikit.common.R.string.amity_max_image_selected)) - } else { - val isSupportPhotoPicker = ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext()) - if (isSupportPhotoPicker && ::imagePickerLauncher.isInitialized) { - val intent = AmityImagePickerActivity.newIntent( - context = requireContext(), - maxItems = AmityConstants.MAX_SELECTION_COUNT - currentCount - ) - imagePickerLauncher.launch(intent) - } else { - Matisse.from(this) - .choose(MimeType.of(MimeType.JPEG, MimeType.PNG, MimeType.GIF)) - .countable(true) - .maxSelectable(AmityConstants.MAX_SELECTION_COUNT - currentCount) - .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - .imageEngine(GlideEngine()) - .theme(com.amity.socialcloud.uikit.common.R.style.AmityImagePickerTheme) - .forResult(AmityConstants.PICK_IMAGES) - } - } - } else { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - pickMultipleImagesPermission.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - pickMultipleImagesPermission.launch(Manifest.permission.READ_MEDIA_IMAGES) - } - } + imagePickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } override fun onFilePicked(data: Uri?) { @@ -616,39 +570,7 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), textView.setShape(null, null, null, null, R.color.amityColorBase, null, null) layout.showSnackBar("", Snackbar.LENGTH_SHORT) } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == 100001) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - isImagePermissionGranted = true - pickMultipleImages() - } else { - isImagePermissionGranted = false - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK && requestCode == 100001) { - pickMultipleImages() - } - if (resultCode == Activity.RESULT_OK && requestCode == AmityConstants.PICK_IMAGES) { - if (requestCode == AmityConstants.PICK_IMAGES) { - data?.let { - val imageUriList = Matisse.obtainResult(it) - for (uri in imageUriList) { - addImageToList(uri) - } - } - if (messageListViewModel.showComposeBar.get()) { - messageListViewModel.showComposeBar.set(false) - } - } else { - super.onActivityResult(requestCode, resultCode, data) - } - } - } + private fun addImageToList(uri: Uri) { disposable.add(messageListViewModel.sendImageMessage(uri) @@ -666,10 +588,6 @@ class AmityChatRoomWithDefaultComposeBarFragment : AmityPickerFragment(), //inflater.inflate(R.menu.eko_chat_list, menu) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return super.onOptionsItemSelected(item) - } - override fun onPause() { super.onPause() messageListViewModel.isRecording.set(false) diff --git a/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithTextComposeBarFragment.kt b/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithTextComposeBarFragment.kt index 0f45d55d..4ef70ba0 100644 --- a/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithTextComposeBarFragment.kt +++ b/chat/src/main/java/com/amity/socialcloud/uikit/chat/messages/fragment/AmityChatRoomWithTextComposeBarFragment.kt @@ -1,9 +1,5 @@ package com.amity.socialcloud.uikit.chat.messages.fragment -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.content.pm.ActivityInfo import android.net.Uri import android.os.Bundle import android.os.Handler @@ -13,8 +9,6 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.activity.OnBackPressedCallback -import androidx.activity.result.ActivityResultLauncher -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar @@ -35,7 +29,6 @@ import com.amity.socialcloud.uikit.chat.databinding.AmityFragmentChatWithTextCom import com.amity.socialcloud.uikit.chat.messages.adapter.AmityMessagePagingAdapter import com.amity.socialcloud.uikit.chat.messages.viewModel.AmityChatRoomEssentialViewModel import com.amity.socialcloud.uikit.chat.messages.viewModel.AmityMessageListViewModel -import com.amity.socialcloud.uikit.common.base.AmityImagePickerActivity import com.amity.socialcloud.uikit.common.base.AmityPickerFragment import com.amity.socialcloud.uikit.common.common.setShape import com.amity.socialcloud.uikit.common.common.showSnackBar @@ -45,12 +38,8 @@ import com.amity.socialcloud.uikit.common.components.AmityAudioRecorderListener import com.amity.socialcloud.uikit.common.components.AmityMessageListListener import com.amity.socialcloud.uikit.common.model.AmityEventIdentifier import com.amity.socialcloud.uikit.common.utils.AmityAndroidUtil -import com.amity.socialcloud.uikit.common.utils.AmityConstants import com.amity.socialcloud.uikit.common.utils.AmityRecyclerViewItemDecoration import com.google.android.material.snackbar.Snackbar -import com.zhihu.matisse.Matisse -import com.zhihu.matisse.MimeType -import com.zhihu.matisse.engine.impl.GlideEngine import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.schedulers.Schedulers @@ -73,20 +62,7 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), private var viewHolderListener: AmityMessagePagingAdapter.CustomViewHolderListener? = null private var messageListDisposable: Disposable? = null private var currentCount = 0 - private var isImagePermissionGranted = false private var isReachBottom = true - private lateinit var imagePickerLauncher: ActivityResultLauncher - - private val pickMultipleImagesPermission = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (it) { - isImagePermissionGranted = true - pickMultipleImages() - } else { - isImagePermissionGranted = false - view?.showSnackBar("Permission denied", Snackbar.LENGTH_SHORT) - } - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -118,7 +94,6 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), setupComposebar() observeViewModelEvents() initMessageLoader() - registerImagePickerResult() // observeRefreshStatus() // observeConnectionStatus() } @@ -168,20 +143,6 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), .subscribe() } */ - - private fun registerImagePickerResult() { - imagePickerLauncher = requireActivity().registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.let { - val uris = it.let(AmityImagePickerActivity::getUris)?.toList() - if (!uris.isNullOrEmpty()) { - uris.map(::addImageToList) - } - } - } - } - } private fun presentDisconnectedView() { if (essentialViewModel.enableConnectionBar) { @@ -396,7 +357,6 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), when (event.type) { AmityEventIdentifier.CAMERA_CLICKED -> takePicture() AmityEventIdentifier.PICK_FILE -> pickFile() - AmityEventIdentifier.PICK_IMAGE -> pickMultipleImages() AmityEventIdentifier.MSG_SEND_ERROR -> { CoroutineScope(Dispatchers.Main).launch { val snackBar = @@ -448,35 +408,6 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), } } - private fun pickMultipleImages() { - if (isImagePermissionGranted) { - currentCount = 0 - if (currentCount == AmityConstants.MAX_SELECTION_COUNT) { - view?.showSnackBar(getString(com.amity.socialcloud.uikit.common.R.string.amity_max_image_selected)) - } else { - val isSupportPhotoPicker = ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext()) - if (isSupportPhotoPicker && ::imagePickerLauncher.isInitialized) { - val intent = AmityImagePickerActivity.newIntent( - context = requireContext(), - maxItems = AmityConstants.MAX_SELECTION_COUNT - currentCount - ) - imagePickerLauncher.launch(intent) - } else { - Matisse.from(this) - .choose(MimeType.of(MimeType.JPEG, MimeType.PNG, MimeType.GIF)) - .countable(true) - .maxSelectable(AmityConstants.MAX_SELECTION_COUNT - currentCount) - .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - .imageEngine(GlideEngine()) - .theme(com.amity.socialcloud.uikit.common.R.style.AmityImagePickerTheme) - .forResult(AmityConstants.PICK_IMAGES) - } - } - } else { - pickMultipleImagesPermission.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - } - override fun onFilePicked(data: Uri?) { view?.showSnackBar("$data", Snackbar.LENGTH_SHORT) } @@ -529,32 +460,6 @@ class AmityChatRoomWithTextComposeBarFragment() : AmityPickerFragment(), layout.showSnackBar("", Snackbar.LENGTH_SHORT) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK && requestCode == AmityConstants.PICK_IMAGES) { - if (requestCode == AmityConstants.PICK_IMAGES) { - data?.let { - val imageUriList = Matisse.obtainResult(it) - for (uri in imageUriList) { - disposable.add(messageListViewModel.sendImageMessage(uri) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnComplete { - msgSent = true - }.doOnError { - msgSent = false - }.subscribe() - ) - } - } - if (messageListViewModel.showComposeBar.get()) { - messageListViewModel.showComposeBar.set(false) - } - } else { - super.onActivityResult(requestCode, resultCode, data) - } - } - } - override fun onPause() { super.onPause() messageListViewModel.isRecording.set(false) diff --git a/chat/src/main/java/com/amity/socialcloud/uikit/chat/recent/fragment/AmityRecentChatFragment.kt b/chat/src/main/java/com/amity/socialcloud/uikit/chat/recent/fragment/AmityRecentChatFragment.kt index 3b1db64d..6e72b53a 100644 --- a/chat/src/main/java/com/amity/socialcloud/uikit/chat/recent/fragment/AmityRecentChatFragment.kt +++ b/chat/src/main/java/com/amity/socialcloud/uikit/chat/recent/fragment/AmityRecentChatFragment.kt @@ -18,7 +18,7 @@ import com.amity.socialcloud.uikit.chat.recent.adapter.AmityRecentChatAdapter import com.amity.socialcloud.uikit.chat.util.AmityRecentItemDecoration import io.reactivex.rxjava3.disposables.Disposable -class AmityRecentChatFragment private constructor() : Fragment(), AmityRecentChatItemClickListener { +class AmityRecentChatFragment : Fragment(), AmityRecentChatItemClickListener { private lateinit var mViewModel: AmityRecentChatViewModel private lateinit var mAdapter: AmityRecentChatAdapter diff --git a/common-compose/src/main/AndroidManifest.xml b/common-compose/src/main/AndroidManifest.xml index 6a268f63..8e9d5da4 100644 --- a/common-compose/src/main/AndroidManifest.xml +++ b/common-compose/src/main/AndroidManifest.xml @@ -11,8 +11,8 @@ - - + + diff --git a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/base/AmityBaseElement.kt b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/base/AmityBaseElement.kt index 893dbbd4..69c3b42f 100644 --- a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/base/AmityBaseElement.kt +++ b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/base/AmityBaseElement.kt @@ -9,10 +9,10 @@ import com.amity.socialcloud.uikit.common.ui.scope.rememberAmityComposeScopeProv @Composable fun AmityBaseElement( - pageScope: AmityComposePageScope? = null, - componentScope: AmityComposeComponentScope? = null, - elementId: String, - content: @Composable AmityComposeElementScope.() -> Unit + pageScope: AmityComposePageScope? = null, + componentScope: AmityComposeComponentScope? = null, + elementId: String, + content: @Composable AmityComposeElementScope.() -> Unit ) { val comp = rememberAmityComposeScopeProvider( pageScope = pageScope, diff --git a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/elements/AmityExpandableTextView.kt b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/elements/AmityExpandableTextView.kt index 0c63bbdd..f1a13219 100644 --- a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/elements/AmityExpandableTextView.kt +++ b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/elements/AmityExpandableTextView.kt @@ -124,9 +124,9 @@ fun AmityExpandableText( } } val layoutResult = remember { mutableStateOf(null) } - val pressIndicator = Modifier.pointerInput(Unit) { + val pressIndicator = Modifier.pointerInput(annotatedString) { detectTapGestures( - onPress = { offset -> + onTap = { offset -> layoutResult.value?.let { val position = it.getOffsetForPosition(offset) val annotations = annotatedString.getStringAnnotations( diff --git a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/scope/AmityComposeScopeImpl.kt b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/scope/AmityComposeScopeImpl.kt index 3d752283..9a7b28c5 100644 --- a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/scope/AmityComposeScopeImpl.kt +++ b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/ui/scope/AmityComposeScopeImpl.kt @@ -114,10 +114,13 @@ internal class AmityComposeComponentScopeImpl( override fun getAccessibilityId(viewId: String): String { val sb = StringBuilder() - sb.append(componentId) + sb.append(pageId) sb.append("/") + sb.append(componentId) + sb.append("/*") if (viewId.isNotEmpty()) { + sb.append("_") sb.append(viewId) } return sb.toString() diff --git a/common-compose/src/main/java/com/amity/socialcloud/uikit/common/utils/AmityNumberExt.kt b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/utils/AmityNumberExt.kt new file mode 100644 index 00000000..21375357 --- /dev/null +++ b/common-compose/src/main/java/com/amity/socialcloud/uikit/common/utils/AmityNumberExt.kt @@ -0,0 +1,17 @@ +package com.amity.socialcloud.uikit.common.utils + +import java.util.Locale +import java.util.concurrent.TimeUnit + +fun Number.formatVideoDuration(): String { + val seconds = this.toLong() + val hours = TimeUnit.SECONDS.toHours(seconds) + val minutes = TimeUnit.SECONDS.toMinutes(seconds) % 60 + val remainingSeconds = seconds % 60 + val formattedDuration = when { + hours > 0 -> String.format(Locale.US, "%d:%02d:%02d", hours, minutes, remainingSeconds) + else -> String.format(Locale.US, "%d:%02d", minutes, remainingSeconds) + } + + return formattedDuration +} \ No newline at end of file diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml index 4448c64f..c7b9f989 100644 --- a/common/src/main/AndroidManifest.xml +++ b/common/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/assets/config.json b/common/src/main/assets/config.json index 5f166ada..60e71ff8 100644 --- a/common/src/main/assets/config.json +++ b/common/src/main/assets/config.json @@ -251,8 +251,8 @@ }, "social_home_page/my_communities/community_category_name": {}, "social_home_page/my_communities/community_members_count": {}, - "social_home_page/newsfeed_component/*": {}, - "social_home_page/global_feed_component/*": {}, + "social_home_page/newsfeed/*": {}, + "social_home_page/global_feed/*": {}, "post_detail_page/*/back_button": { "icon": "backIcon" }, @@ -438,13 +438,7 @@ "community_profile_page/*/menu_button": { "image": "threeDotIcon" }, - "community_profile_page/community_profile_tab/*": {}, - "community_profile_page/community_profile_tab/community_feed_tab_button": { - "image": "communityFeedIcon" - }, - "community_profile_page/community_profile_tab/community_pin_tab_button": { - "image": "communityPinIcon" - }, + "community_profile_page/*/community_profile_tab": {}, "community_profile_page/community_feed/*": {}, "community_profile_page/community_pin/*": {}, "community_profile_page/*/community_create_post_button": { @@ -505,6 +499,14 @@ "text": "Save", "image": "" }, + "community_setup_page/*/image_button": { + "text": "Photo", + "image": "postAttachmentPhoto" + }, + "community_setup_page/*/camera_button": { + "text": "Camera", + "image": "postAttachmentCamera" + }, "community_add_category_page/*/*": {}, "community_add_member_page/*/*": {}, "community_membership_page/*/*": {}, diff --git a/common/src/main/java/com/amity/socialcloud/uikit/common/base/AmityPickerFragment.kt b/common/src/main/java/com/amity/socialcloud/uikit/common/base/AmityPickerFragment.kt index d875c65f..a6271157 100644 --- a/common/src/main/java/com/amity/socialcloud/uikit/common/base/AmityPickerFragment.kt +++ b/common/src/main/java/com/amity/socialcloud/uikit/common/base/AmityPickerFragment.kt @@ -3,11 +3,13 @@ package com.amity.socialcloud.uikit.common.base import android.Manifest import android.net.Uri import android.os.Build +import android.os.Bundle +import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts -import com.amity.socialcloud.uikit.common.R import com.amity.socialcloud.uikit.common.common.showSnackBar import com.amity.socialcloud.uikit.common.contract.AmityPickFileContract -import com.amity.socialcloud.uikit.common.contract.AmityPickImageContract import com.amity.socialcloud.uikit.common.utils.AmityCameraUtil import com.google.android.material.snackbar.Snackbar import java.io.File @@ -16,18 +18,7 @@ abstract class AmityPickerFragment : AmityBaseFragment() { private var photoFile: File? = null - private val pickImage = registerForActivityResult(AmityPickImageContract()) { data -> - onImagePicked(data) - } - - private val pickImagePermission = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (it) { - pickImage.launch(getString(R.string.amity_choose_image)) - } else { - view?.showSnackBar("Permission denied", Snackbar.LENGTH_SHORT) - } - } + private lateinit var imagePickerLauncher: ActivityResultLauncher private val pickFile = registerForActivityResult(AmityPickFileContract()) { data -> onFilePicked(data) @@ -70,13 +61,7 @@ abstract class AmityPickerFragment : AmityBaseFragment() { abstract fun onPhotoClicked(file: File?) fun pickImage() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - pickImagePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - pickImagePermission.launch(Manifest.permission.READ_MEDIA_IMAGES) - } else { - pickImagePermission.launch(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) - } + imagePickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } fun pickFile() { @@ -97,4 +82,11 @@ abstract class AmityPickerFragment : AmityBaseFragment() { cameraPermission.launch(permissions) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + imagePickerLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + onImagePicked(uri) + } + } + } \ No newline at end of file diff --git a/common/src/main/java/com/amity/socialcloud/uikit/common/config/AmityUIKitDrawableResolver.kt b/common/src/main/java/com/amity/socialcloud/uikit/common/config/AmityUIKitDrawableResolver.kt index 3769adb0..cbd52d3f 100644 --- a/common/src/main/java/com/amity/socialcloud/uikit/common/config/AmityUIKitDrawableResolver.kt +++ b/common/src/main/java/com/amity/socialcloud/uikit/common/config/AmityUIKitDrawableResolver.kt @@ -38,8 +38,6 @@ object AmityUIKitDrawableResolver { "storyCreate" to R.drawable.amity_ic_create_story_social, "verifiedBadge" to R.drawable.amity_ic_verified, "plusIcon" to R.drawable.amity_ic_plus_button, - "communityFeedIcon" to R.drawable.amity_ic_community_feed, - "communityPinIcon" to R.drawable.amity_ic_community_pin, "backIcon" to R.drawable.amity_ic_back, "threeDotIcon" to R.drawable.amity_ic_more_horiz, "communityAnnouncementBadge" to R.drawable.amity_ic_announcement_badge, diff --git a/common/src/main/res/drawable/amity_ic_community_image_feed.xml b/common/src/main/res/drawable/amity_ic_community_image_feed.xml new file mode 100644 index 00000000..ef62cf2c --- /dev/null +++ b/common/src/main/res/drawable/amity_ic_community_image_feed.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/common/src/main/res/drawable/amity_ic_community_pin.xml b/common/src/main/res/drawable/amity_ic_community_pin.xml index 864fd5f5..43cb7ddb 100644 --- a/common/src/main/res/drawable/amity_ic_community_pin.xml +++ b/common/src/main/res/drawable/amity_ic_community_pin.xml @@ -3,11 +3,12 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + diff --git a/common/src/main/res/drawable/amity_ic_community_video_feed.xml b/common/src/main/res/drawable/amity_ic_community_video_feed.xml new file mode 100644 index 00000000..cef4b3b0 --- /dev/null +++ b/common/src/main/res/drawable/amity_ic_community_video_feed.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/main/.gitignore b/main/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/main/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/main/build.gradle b/main/build.gradle deleted file mode 100644 index b032fd4e..00000000 --- a/main/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -apply from: "../buildsystem/activity.gradle" - -dependencies { - api project(path: ':common') - api project(path: ':chat') - api project(path: ':social') -} \ No newline at end of file diff --git a/main/consumer-rules.pro b/main/consumer-rules.pro deleted file mode 100644 index e69de29b..00000000 diff --git a/main/proguard-rules.pro b/main/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/main/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/main/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt b/main/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt deleted file mode 100644 index cc5851e7..00000000 --- a/main/src/androidTest/java/com/amity/socialcloud/uikit/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.amity.socialcloud.uikit - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.runner.AndroidJUnit4 -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.amity.socialcloud.uikit.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/main/src/main/AndroidManifest.xml b/main/src/main/AndroidManifest.xml deleted file mode 100644 index dfdf6569..00000000 --- a/main/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/main/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt b/main/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt deleted file mode 100644 index 8d71de8b..00000000 --- a/main/src/test/java/com/amity/socialcloud/uikit/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.amity.socialcloud.uikit - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 04f0a555..262901f7 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -11,12 +11,12 @@ android:maxSdkVersion="32" /> - - + + - + diff --git a/settings.gradle b/settings.gradle index 1efd4818..4ad41371 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,11 +1,9 @@ include ':amity-uikit' rootProject.name = 'UpstraUIKitAndroid' -//include ':app' include ':chat' include ':sample' include ':common' include ':social' -//include ':main' include ':social-compose' include ':amity-sample-code' include ':chat-compose' diff --git a/social-compose/src/main/AndroidManifest.xml b/social-compose/src/main/AndroidManifest.xml index 2d9ec70e..193625c4 100644 --- a/social-compose/src/main/AndroidManifest.xml +++ b/social-compose/src/main/AndroidManifest.xml @@ -38,7 +38,8 @@ android:name=".post.detail.AmityPostDetailPageActivity" android:configChanges="uiMode" android:screenOrientation="portrait" - android:theme="@style/AmitySocialTheme" /> + android:theme="@style/AmitySocialTheme" + android:windowSoftInputMode="adjustResize" /> - Box( - modifier = Modifier + var selectedTabIndex by remember { mutableIntStateOf(0) } + Scaffold { padding -> + Box( + modifier = Modifier .padding(PaddingValues(bottom = padding.calculateBottomPadding())) .fillMaxSize() .pullRefresh(pullRefreshState) .background(AmityTheme.colors.newsfeedDivider), - ) { - LaunchedEffect(lazyListState) { - snapshotFlow { lazyListState.firstVisibleItemIndex } - .collect { index -> - if (index > 1 && !isHeaderSticky) { - isHeaderSticky = true - } else if (index <= 1 && isHeaderSticky) { - isHeaderSticky = false + ) { + LaunchedEffect(lazyListState) { + snapshotFlow { lazyListState.firstVisibleItemIndex } + .collect { index -> + if (index > 1 && !isHeaderSticky) { + isHeaderSticky = true + } else if (index <= 1 && isHeaderSticky) { + isHeaderSticky = false + } + } + } - } - } - } - LazyColumn( - modifier = Modifier + LazyColumn( + state = lazyListState, + modifier = Modifier .fillMaxSize() - .background(AmityTheme.colors.newsfeedDivider), - state = lazyListState, - ) { - if (community == null || state.isRefreshing) { - item { - AmityCommunityProfileShimmer() - } - } else { - stickyHeader { - if (isHeaderSticky) { - AmityCommunityHeaderComponent( - pageScope = getPageScope(), - community = community!!, - style = AmityCommunityHeaderStyle.COLLAPSE, - ) - AmityCommunityProfileTabComponent( - pageScope = getPageScope(), - selectedIndex = selectedTabIndex, - ) { index -> - selectedTabIndex = index - } - } - } - item { - AmityCommunityHeaderComponent( - pageScope = getPageScope(), - community = community!!, - style = AmityCommunityHeaderStyle.EXPANDED, - ) - } - } - item { - AmityCommunityProfileTabComponent( - pageScope = getPageScope(), - selectedIndex = selectedTabIndex, - ) { index -> - selectedTabIndex = index - } - } - if (announcement.itemCount > 0) { - items( - count = announcement.itemCount, - key = { - "announcement_${announcement[it]?.post?.getPostId() ?: it}" - } - ) { index -> - announcement[index]?.post?.let { post -> - // TODO: 3/6/24 currently only support text, image, and video post - when (post.getData()) { - is AmityPost.Data.TEXT, - is AmityPost.Data.IMAGE, - is AmityPost.Data.VIDEO -> { - } + .background(AmityTheme.colors.background), + ) { + if (community == null || state.isRefreshing) { + item { + AmityCommunityProfileShimmer() + } + } else { + stickyHeader { + if (isHeaderSticky) { + AmityCommunityHeaderComponent( + pageScope = getPageScope(), + community = community!!, + style = AmityCommunityHeaderStyle.COLLAPSE, + ) + AmityCommunityProfileTabRow( + pageScope = getPageScope(), + selectedIndex = selectedTabIndex, + ) { index -> + selectedTabIndex = index + } + } + } + + item { + AmityCommunityHeaderComponent( + pageScope = getPageScope(), + community = community!!, + style = AmityCommunityHeaderStyle.EXPANDED, + ) + } + } + item { + AmityCommunityProfileTabRow( + pageScope = getPageScope(), + selectedIndex = selectedTabIndex, + ) { index -> + selectedTabIndex = index + } + } + if (announcement.itemCount > 0 && (selectedTabIndex == 0 || selectedTabIndex == 1)) { + items( + count = announcement.itemCount, + key = { + "announcement_${announcement[it]?.post?.getPostId() ?: it}" + } + ) { index -> + announcement[index]?.post?.let { post -> + // TODO: 3/6/24 currently only support text, image, and video post + when (post.getData()) { + is AmityPost.Data.TEXT, + is AmityPost.Data.IMAGE, + is AmityPost.Data.VIDEO -> { + } else -> return@items - } + } if (post.isDeleted()) { - return@items - } + return@items + } AmityPostContentComponent( - post = post, - style = AmityPostContentComponentStyle.FEED, - category = AmityPostCategory.ANNOUNCEMENT, - hideMenuButton = false, - hideTarget = true, - ) { + post = post, + style = AmityPostContentComponentStyle.FEED, + category = AmityPostCategory.ANNOUNCEMENT, + hideMenuButton = false, + hideTarget = true, + ) { behavior.goToPostDetailPage( AmityCommunityProfilePageBehavior.Context( pageContext = context, @@ -182,52 +202,53 @@ fun AmityCommunityProfilePage( postId = post.getPostId(), category = AmityPostCategory.ANNOUNCEMENT ) - } - AmityNewsFeedDivider() - } - } - } - if (selectedTabIndex == 0) { - items( - count = posts.itemCount, - key = { - (posts[it] as? AmityListItem.PostItem)?.post?.getPostId() ?: it - } - ) { index -> - when (val data = posts[index]) { - is AmityListItem.PostItem -> { - val post = data.post - val isAnnouncement = announcement.itemSnapshotList.items - .map { it.postId } - .contains(post.getPostId()) + } + AmityNewsFeedDivider() + } + } + } + if (selectedTabIndex == 0) { + items( + count = posts.itemCount, + key = { + (posts[it] as? AmityListItem.PostItem)?.post?.getPostId() ?: it + } + ) { index -> + when (val data = posts[index]) { + is AmityListItem.PostItem -> { + val post = data.post + val isAnnouncement = announcement.itemSnapshotList.items + .map { it.postId } + .contains(post.getPostId()) if (isAnnouncement) { - return@items - } + return@items + } // TODO: 3/6/24 currently only support text, image, and video post - when (post.getData()) { - is AmityPost.Data.TEXT, - is AmityPost.Data.IMAGE, - is AmityPost.Data.VIDEO -> { - } + when (post.getData()) { + is AmityPost.Data.TEXT, + is AmityPost.Data.IMAGE, + is AmityPost.Data.VIDEO -> { + } else -> return@items - } - val category = if( - pin.itemSnapshotList.items.map { it.postId }.contains(post.getPostId()) - ) { - AmityPostCategory.PIN - } else { - AmityPostCategory.GENERAL - } - AmityPostContentComponent( - post = post, - style = AmityPostContentComponentStyle.FEED, - category = category, - hideMenuButton = false, - hideTarget = true, - ) { + } + val category = if ( + pin.itemSnapshotList.items.map { it.postId } + .contains(post.getPostId()) + ) { + AmityPostCategory.PIN + } else { + AmityPostCategory.GENERAL + } + AmityPostContentComponent( + post = post, + style = AmityPostContentComponentStyle.FEED, + category = category, + hideMenuButton = false, + hideTarget = true, + ) { behavior.goToPostDetailPage( AmityCommunityProfilePageBehavior.Context( pageContext = context, @@ -235,70 +256,71 @@ fun AmityCommunityProfilePage( postId = post.getPostId(), category = category, ) - } - AmityNewsFeedDivider() - } + } + AmityNewsFeedDivider() + } is AmityListItem.AdItem -> { - val ad = data.ad - + val ad = data.ad AmityPostAdView( - ad = ad, - ) - AmityNewsFeedDivider() - } + ad = ad, + ) + AmityNewsFeedDivider() + } - else -> {} - } - } + else -> { + AmityPostShimmer() + AmityNewsFeedDivider() + } + } + } if (announcement.itemCount == 0 && posts.itemCount == 0) { - item { - Box( - modifier = Modifier - .height(480.dp), - ) { - AmityCommunityEmptyView() - } - } - } - } else if (selectedTabIndex == 1) { - items( - count = pin.itemCount, - key = { - "pin_${pin[it]?.post?.getPostId() ?: it}" - } - ) { index -> - pin[index]?.post?.let { post -> - val isAnnouncement = announcement.itemSnapshotList.items - .map { it.postId } - .contains(post.getPostId()) + item { + Box( + modifier = Modifier + .height(480.dp), + ) { + AmityCommunityEmptyView() + } + } + } + } else if (selectedTabIndex == 1) { + items( + count = pin.itemCount, + key = { + "pin_${pin[it]?.post?.getPostId() ?: it}" + } + ) { index -> + pin[index]?.post?.let { post -> + val isAnnouncement = announcement.itemSnapshotList.items + .map { it.postId } + .contains(post.getPostId()) if (isAnnouncement) { - return@items - } + return@items + } // TODO: 3/6/24 currently only support text, image, and video post - when (post.getData()) { - is AmityPost.Data.TEXT, - is AmityPost.Data.IMAGE, - is AmityPost.Data.VIDEO -> { - } + when (post.getData()) { + is AmityPost.Data.TEXT, + is AmityPost.Data.IMAGE, + is AmityPost.Data.VIDEO -> { + } else -> return@items - } + } if (post.isDeleted()) { - return@items - } - + return@items + } AmityPostContentComponent( - post = post, - style = AmityPostContentComponentStyle.FEED, - category = AmityPostCategory.PIN, - hideMenuButton = false, - hideTarget = true, - ) { + post = post, + style = AmityPostContentComponentStyle.FEED, + category = AmityPostCategory.PIN, + hideMenuButton = false, + hideTarget = true, + ) { behavior.goToPostDetailPage( AmityCommunityProfilePageBehavior.Context( pageContext = context, @@ -306,61 +328,181 @@ fun AmityCommunityProfilePage( postId = post.getPostId(), category = AmityPostCategory.PIN ) - } - AmityNewsFeedDivider() - } - } - if (announcement.itemCount == 0 && pin.itemCount == 0) { - item { - Box( - modifier = Modifier - .height(480.dp), - ) { - AmityCommunityEmptyView() - } - } - } + } + AmityNewsFeedDivider() + } + } + if (announcement.itemCount == 0 && pin.itemCount == 0) { + item { + Box( + modifier = Modifier + .height(480.dp), + ) { + AmityCommunityEmptyView() + } + } + } + } else if (selectedTabIndex == 2) { + item { Spacer(modifier.height(12.dp)) } + /* count calculation logic + * showing 2 items in a row + * check if the item count is even or odd + * if even, show 2 items in a row + * if odd, show last item in a new row + */ + items( + count = imagePosts.itemCount / 2 + imagePosts.itemCount % 2, + key = { + "image_${imagePosts[it]?.getPostId() ?: it}" + } + ) { index -> + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 2.dp) + .aspectRatio(2f) + ) { + // check first image index is valid + val isFirstImageIndexValid = + index * 2 < imagePosts.itemCount && index >= 0 + val firstImage = if (isFirstImageIndexValid) { + imagePosts[index * 2] + } else { + null + } + AmityCommunityImageFeedItem( + modifier = modifier.weight(1f), + data = firstImage?.getData() as? AmityPost.Data.IMAGE + ) + + // check second image index is valid + val isSecondImageIndexValid = + index * 2 + 1 < imagePosts.itemCount && index >= 0 + val secondImage = if (isSecondImageIndexValid) { + imagePosts[index * 2 + 1] + } else { + null + } + + // show image thumbnail if index is valid + // if not, it's one last item in a new row and show empty box + if (isSecondImageIndexValid) { + AmityCommunityImageFeedItem( + modifier = modifier.weight(1f), + data = secondImage?.getData() as? AmityPost.Data.IMAGE + ) + } else { + Box(modifier = modifier.weight(1f)) + } + } + } + + if (imagePosts.itemCount == 0) { + item { + Box(modifier = Modifier.height(300.dp)) { + AmityCommunityEmptyImageFeedView() + } + } + } + } else if (selectedTabIndex == 3) { + item { Spacer(modifier.height(12.dp)) } + items( + count = videoPosts.itemCount / 2 + videoPosts.itemCount % 2, + key = { + "video_${videoPosts[it]?.getPostId() ?: it}" + } + ) { index -> + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 2.dp) + .aspectRatio(2f) + ) { + val isFirstVideoIndexValid = + index * 2 < videoPosts.itemCount && index >= 0 + + if (isFirstVideoIndexValid) { + val firstVideo = videoPosts[index * 2] + if (firstVideo == null) { + Box(modifier.weight(1f)) + } else { + AmityCommunityVideoFeedItem( + modifier = modifier.weight(1f), + data = firstVideo.getData() as AmityPost.Data.VIDEO + ) + } + } else { + Box(modifier.weight(1f)) + } + val isSecondVideoIndexValid = + index * 2 + 1 < videoPosts.itemCount && index >= 0 + + if (isSecondVideoIndexValid) { + val secondVideo = videoPosts.peek(index * 2 + 1) + if (secondVideo == null) { + Box(modifier = modifier.weight(1f)) + } else { + AmityCommunityVideoFeedItem( + modifier = modifier.weight(1f), + data = secondVideo.getData() as AmityPost.Data.VIDEO + ) + } + } else { + Box(modifier = modifier.weight(1f)) + } + } + } + + if (videoPosts.itemCount == 0) { + item { + Box(modifier = Modifier.height(300.dp)) { + AmityCommunityEmptyVideoFeedView() + } + } + } } - } - AmityBaseElement( - pageScope = getPageScope(), - elementId = "community_create_post_button", - ) { - FloatingActionButton( - onClick = { - expanded = true - }, - shape = RoundedCornerShape(size = 32.dp), - containerColor = AmityTheme.colors.primary, - modifier = Modifier + } + AmityBaseElement( + pageScope = getPageScope(), + elementId = "community_create_post_button", + ) { + FloatingActionButton( + onClick = { + expanded = true + }, + shape = RoundedCornerShape(size = 32.dp), + containerColor = AmityTheme.colors.primary, + modifier = Modifier .padding(16.dp) .size(64.dp) .align(Alignment.BottomEnd) - ) { - Icon( - painter = painterResource(id = getConfig().getIcon()), - contentDescription = "create post", - tint = Color.White, - modifier = Modifier.size(32.dp) - ) - } - } - if (community != null) { - AmityCommunityProfileActionsBottomSheet( - modifier = Modifier, - community = community!!, - shouldShow = expanded, - onDismiss = { expanded = false }, - ) - } - PullRefreshIndicator( - refreshing = state.isRefreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - } - } - } + ) { + Icon( + painter = painterResource(id = getConfig().getIcon()), + contentDescription = "create post", + tint = Color.White, + modifier = Modifier.size(32.dp) + ) + } + } + if (community != null) { + AmityCommunityProfileActionsBottomSheet( + modifier = Modifier, + community = community!!, + shouldShow = expanded, + onDismiss = { expanded = false }, + ) + } + PullRefreshIndicator( + refreshing = state.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) + } + } + } } \ No newline at end of file diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfilePageActivity.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfilePageActivity.kt index cc249ce9..00d009b6 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfilePageActivity.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfilePageActivity.kt @@ -9,21 +9,21 @@ import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity class AmityCommunityProfilePageActivity : AppCompatActivity() { - + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - + val communityId = intent.getStringExtra(EXTRA_PARAM_COMMUNITY_ID) ?: "" - + setContent { - AmityCommunityProfilePage(communityId) + AmityCommunityProfilePage(communityId = communityId) } } - + companion object { private const val EXTRA_PARAM_COMMUNITY_ID = "community_id" - + fun newIntent( context: Context, communityId: String diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfileViewModel.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfileViewModel.kt index 295e21ae..5e00e70c 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfileViewModel.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/AmityCommunityProfileViewModel.kt @@ -21,92 +21,134 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch import java.util.concurrent.TimeUnit -class AmityCommunityProfileViewModel constructor(private val communityId: String) : AmityBaseViewModel() { - val disposable = CompositeDisposable() - - private val _communityProfileState by lazy { - MutableStateFlow(CommunityProfileState.Initial(communityId)) - } - - val communityProfileState get() = _communityProfileState - - init { - refresh() - } - - fun refresh() { - disposable.clear() - _communityProfileState.value = CommunityProfileState.Initial(communityId) - Flowable.combineLatest( - AmitySocialClient.newCommunityRepository().getCommunity(communityId).doOnError { }, - AmityCoreClient.hasPermission(AmityPermission.EDIT_COMMUNITY).atCommunity(communityId).check().onErrorReturn { communityProfileState.value.isModerator } - ) { community, hasPermission -> Pair(community, hasPermission) } - .doOnNext { (community, isModerator) -> - val isMember = community.isJoined() - _communityProfileState.value = CommunityProfileState(communityId, community, isRefreshing = false, isMember = isMember, isModerator = isModerator) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe() - .let { disposable.add(it) } - } - - fun getAnnouncement(): Flow> { - return AmitySocialClient.newPostRepository() - .getPinnedPosts( - communityId = communityId, - placement = AmityPinnedPost.PinPlacement.ANNOUNCEMENT.value) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .asFlow() - } - - fun getPin(): Flow> { - return AmitySocialClient.newPostRepository() - .getPinnedPosts( - communityId = communityId, - placement = AmityPinnedPost.PinPlacement.DEFAULT.value) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .asFlow() - } - - fun getCommunityPosts(): Flow> { - val injector = AmityAdInjector( - placement = AmityAdPlacement.FEED, - communityId = communityId, - ) - - return AmitySocialClient.newFeedRepository() - .getCommunityFeed(communityId) - .includeDeleted(false) - .build() - .query() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .onBackpressureBuffer() - .throttleLatest(2000, TimeUnit.MILLISECONDS) - .map { injector.inject(it) } - .asFlow() - .catch {} - } +class AmityCommunityProfileViewModel constructor(private val communityId: String) : + AmityBaseViewModel() { + val disposable = CompositeDisposable() + + private val _communityProfileState by lazy { + MutableStateFlow(CommunityProfileState.Initial(communityId)) + } + + val communityProfileState get() = _communityProfileState + + init { + refresh() + } + + fun refresh() { + disposable.clear() + _communityProfileState.value = CommunityProfileState.Initial(communityId) + Flowable.combineLatest( + AmitySocialClient.newCommunityRepository().getCommunity(communityId).doOnError { }, + AmityCoreClient.hasPermission(AmityPermission.EDIT_COMMUNITY).atCommunity(communityId) + .check().onErrorReturn { communityProfileState.value.isModerator } + ) { community, hasPermission -> Pair(community, hasPermission) } + .doOnNext { (community, isModerator) -> + val isMember = community.isJoined() + _communityProfileState.value = CommunityProfileState( + communityId, + community, + isRefreshing = false, + isMember = isMember, + isModerator = isModerator + ) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe() + .let { disposable.add(it) } + } + + fun getAnnouncement(): Flow> { + return AmitySocialClient.newPostRepository() + .getPinnedPosts( + communityId = communityId, + placement = AmityPinnedPost.PinPlacement.ANNOUNCEMENT.value + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .asFlow() + } + + fun getPin(): Flow> { + return AmitySocialClient.newPostRepository() + .getPinnedPosts( + communityId = communityId, + placement = AmityPinnedPost.PinPlacement.DEFAULT.value + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .asFlow() + } + + fun getCommunityPosts(): Flow> { + val injector = AmityAdInjector( + placement = AmityAdPlacement.FEED, + communityId = communityId, + ) + + return AmitySocialClient.newFeedRepository() + .getCommunityFeed(communityId) + .includeDeleted(false) + .build() + .query() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .onBackpressureBuffer() + .throttleLatest(2000, TimeUnit.MILLISECONDS) + .map { injector.inject(it) } + .asFlow() + .catch {} + } + + fun getCommunityImagePosts(): Flow> { + return AmitySocialClient.newPostRepository() + .getPosts() + .targetCommunity(communityId) + .types(listOf(AmityPost.DataType.sealedOf(AmityPost.DataType.IMAGE.getApiKey()))) + .includeDeleted(false) + .build() + .query() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .onBackpressureBuffer() + .throttleLatest(1000, TimeUnit.MILLISECONDS) + .asFlow() + .catch {} + } + + fun getCommunityVideoPosts(): Flow> { + return AmitySocialClient.newPostRepository() + .getPosts() + .targetCommunity(communityId) + .types(listOf(AmityPost.DataType.sealedOf(AmityPost.DataType.VIDEO.getApiKey()))) + .includeDeleted(false) + .build() + .query() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .onBackpressureBuffer() + .throttleLatest(1000, TimeUnit.MILLISECONDS) + .asFlow() + .catch {} + } } data class CommunityProfileState( - val communityId: String, - val community: AmityCommunity? = null, - val isRefreshing: Boolean, - val isMember: Boolean, - val isModerator: Boolean, + val communityId: String, + val community: AmityCommunity? = null, + val isRefreshing: Boolean, + val isMember: Boolean, + val isModerator: Boolean, ) { - - companion object { - fun Initial(communityId: String) = CommunityProfileState( - communityId = communityId, - community = null, - isRefreshing = true, - isMember = false, - isModerator = false, - ) - } + + companion object { + fun Initial(communityId: String) = CommunityProfileState( + communityId = communityId, + community = null, + isRefreshing = true, + isMember = false, + isModerator = false, + ) + } } \ No newline at end of file diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityEmptyView.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityEmptyView.kt index 254de013..66e73f23 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityEmptyView.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityEmptyView.kt @@ -4,11 +4,10 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -27,37 +26,89 @@ import com.amity.socialcloud.uikit.community.compose.R @Composable fun AmityCommunityEmptyView( - modifier: Modifier = Modifier, - pageScope: AmityComposePageScope? = null, - componentScope: AmityComposeComponentScope? = null, + modifier: Modifier = Modifier, + pageScope: AmityComposePageScope? = null, + componentScope: AmityComposeComponentScope? = null, ) { - AmityBaseElement( - pageScope = pageScope, - componentScope = componentScope, - elementId = "empty_feed" - ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(AmityTheme.colors.background), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(id = R.drawable.amity_ic_empty_feed), - contentDescription = "empty feed icon" - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "No post yet", - style = TextStyle( - fontSize = 17.sp, - lineHeight = 22.sp, - fontWeight = FontWeight(600), - color = AmityTheme.colors.baseShade3, - textAlign = TextAlign.Center, - ) - ) - } - } + AmityBaseElement( + pageScope = pageScope, + componentScope = componentScope, + elementId = "empty_feed" + ) { + Column( + modifier = Modifier + .fillMaxSize() + .background(AmityTheme.colors.background), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.amity_ic_empty_feed), + contentDescription = "empty feed icon" + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "No post yet", + style = TextStyle( + fontSize = 17.sp, + lineHeight = 22.sp, + fontWeight = FontWeight(600), + color = AmityTheme.colors.baseShade3, + textAlign = TextAlign.Center, + ) + ) + } + } +} + +@Composable +fun AmityCommunityEmptyImageFeedView( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .background(AmityTheme.colors.background), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.amity_ic_photo_empty), + tint = AmityTheme.colors.baseShade4, + contentDescription = "empty feed icon" + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "No photo yet", + style = AmityTheme.typography.title.copy( + color = AmityTheme.colors.baseShade3, + ), + ) + } +} + +@Composable +fun AmityCommunityEmptyVideoFeedView( + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .background(AmityTheme.colors.background), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(id = R.drawable.amity_ic_video_empty), + tint = AmityTheme.colors.baseShade4, + contentDescription = "empty feed icon" + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "No video yet", + style = AmityTheme.typography.title.copy( + color = AmityTheme.colors.baseShade3, + ), + ) + } } \ No newline at end of file diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityImageFeedItem.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityImageFeedItem.kt new file mode 100644 index 00000000..433e7904 --- /dev/null +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityImageFeedItem.kt @@ -0,0 +1,121 @@ +package com.amity.socialcloud.uikit.community.compose.community.profile.element + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.media3.common.util.UnstableApi +import coil.compose.AsyncImage +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.amity.socialcloud.sdk.model.core.file.AmityImage +import com.amity.socialcloud.sdk.model.social.post.AmityPost +import com.amity.socialcloud.uikit.common.ui.elements.AmityMenuButton +import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme +import com.amity.socialcloud.uikit.common.utils.clickableWithoutRipple +import com.amity.socialcloud.uikit.community.compose.R + +@Composable +fun AmityCommunityImageFeedItem( + modifier: Modifier = Modifier, + data: AmityPost.Data.IMAGE?, +) { + val thumbnailUrl = remember(data) { + data?.getImage()?.getUrl(AmityImage.Size.MEDIUM) + } + + var showPopupDialog by remember { + mutableStateOf(false) + } + + AsyncImage( + model = ImageRequest + .Builder(LocalContext.current) + .data(thumbnailUrl) + .crossfade(true) + .networkCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .memoryCachePolicy(CachePolicy.ENABLED) + .build(), + contentDescription = "Image Post", + contentScale = ContentScale.Crop, + modifier = modifier + .clickableWithoutRipple { + showPopupDialog = true + } + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)) + .background(AmityTheme.colors.baseShade4) + ) + + if (showPopupDialog) { + AmityCommunityImagePreviewDialog(data = data) { + showPopupDialog = false + } + } +} + +@androidx.annotation.OptIn(UnstableApi::class) +@Composable +fun AmityCommunityImagePreviewDialog( + modifier: Modifier = Modifier, + data: AmityPost.Data.IMAGE?, + onDismiss: () -> Unit, +) { + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties( + usePlatformDefaultWidth = false + ), + ) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.Black) + ) { + AsyncImage( + model = ImageRequest + .Builder(LocalContext.current) + .data(data?.getImage()?.getUrl(AmityImage.Size.FULL)) + .crossfade(true) + .networkCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .memoryCachePolicy(CachePolicy.ENABLED) + .build(), + contentDescription = "Image Post", + contentScale = ContentScale.Fit, + modifier = modifier.fillMaxSize() + ) + + AmityMenuButton( + icon = R.drawable.amity_ic_close2, + size = 32.dp, + iconPadding = 10.dp, + tint = Color.Black.copy(0.5f), + background = Color.White.copy(0.8f), + modifier = modifier + .align(Alignment.TopStart) + .offset(16.dp, 32.dp), + onClick = { + onDismiss() + } + ) + } + } +} \ No newline at end of file diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityProfileTabRow.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityProfileTabRow.kt new file mode 100644 index 00000000..fc3fc6d5 --- /dev/null +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityProfileTabRow.kt @@ -0,0 +1,192 @@ +package com.amity.socialcloud.uikit.community.compose.community.profile.element + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.amity.socialcloud.uikit.common.ui.base.AmityBaseElement +import com.amity.socialcloud.uikit.common.ui.scope.AmityComposePageScope +import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme +import com.amity.socialcloud.uikit.common.utils.clickableWithoutRipple +import com.amity.socialcloud.uikit.community.compose.R + +@Composable +fun AmityCommunityProfileTabRow( + modifier: Modifier = Modifier, + pageScope: AmityComposePageScope? = null, + selectedIndex: Int, + onSelect: (Int) -> Unit +) { + AmityBaseElement( + pageScope = pageScope, + elementId = "community_profile_tab", + ) { + Column( + modifier = modifier + .background(color = AmityTheme.colors.background) + .padding(start = 16.dp, top = 16.dp, end = 16.dp) + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(20.dp), + modifier = modifier.fillMaxWidth() + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .weight(1f) + .clickableWithoutRipple { + onSelect(0) + } + ) { + Box( + modifier = Modifier.padding(bottom = 12.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.amity_ic_community_feed), + contentDescription = "", + tint = if (selectedIndex == 0) AmityTheme.colors.base else AmityTheme.colors.secondaryShade3, + modifier = Modifier.size(24.dp) + ) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background( + color = if (selectedIndex == 0) AmityTheme.colors.highlight else Color.Transparent, + shape = RoundedCornerShape( + topStart = 1.dp, + topEnd = 1.dp + ) + ) + ) + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .weight(1f) + .clickableWithoutRipple { + onSelect(1) + } + ) { + Box( + modifier = Modifier.padding(bottom = 12.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.amity_ic_community_pin), + contentDescription = "", + tint = if (selectedIndex == 1) AmityTheme.colors.base else AmityTheme.colors.secondaryShade3, + modifier = Modifier.size(24.dp) + ) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background( + color = if (selectedIndex == 1) AmityTheme.colors.highlight else Color.Transparent, + shape = RoundedCornerShape( + topStart = 1.dp, + topEnd = 1.dp + ) + ) + ) + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .weight(1f) + .clickableWithoutRipple { + onSelect(2) + } + ) { + Box( + modifier = Modifier.padding(bottom = 12.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.amity_ic_community_image_feed), + contentDescription = "", + tint = if (selectedIndex == 2) AmityTheme.colors.base else AmityTheme.colors.secondaryShade3, + modifier = Modifier + .size(24.dp) + .padding(2.dp) + ) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background( + color = if (selectedIndex == 2) AmityTheme.colors.highlight else Color.Transparent, + shape = RoundedCornerShape( + topStart = 1.dp, + topEnd = 1.dp + ) + ) + ) + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .weight(1f) + .clickableWithoutRipple { + onSelect(3) + } + ) { + Box( + modifier = Modifier.padding(bottom = 12.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.amity_ic_community_video_feed), + contentDescription = "", + tint = if (selectedIndex == 3) AmityTheme.colors.base else AmityTheme.colors.secondaryShade3, + modifier = Modifier + .size(24.dp) + .padding(2.dp) + ) + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background( + color = if (selectedIndex == 3) AmityTheme.colors.highlight else Color.Transparent, + shape = RoundedCornerShape( + topStart = 1.dp, + topEnd = 1.dp + ) + ) + ) + } + } + + HorizontalDivider( + thickness = 1.dp, + color = AmityTheme.colors.divider, + modifier = modifier, + ) + } + } +} diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityVideoFeedItem.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityVideoFeedItem.kt new file mode 100644 index 00000000..1a463bc8 --- /dev/null +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/profile/element/AmityCommunityVideoFeedItem.kt @@ -0,0 +1,187 @@ +package com.amity.socialcloud.uikit.community.compose.community.profile.element + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rxjava3.subscribeAsState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import coil.compose.AsyncImage +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.amity.socialcloud.sdk.model.core.file.AmityImage +import com.amity.socialcloud.sdk.model.social.post.AmityPost +import com.amity.socialcloud.uikit.common.ui.elements.AmityMenuButton +import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme +import com.amity.socialcloud.uikit.common.utils.clickableWithoutRipple +import com.amity.socialcloud.uikit.common.utils.formatVideoDuration +import com.amity.socialcloud.uikit.community.compose.R +import com.amity.socialcloud.uikit.community.compose.post.detail.AmityPostVideoPlayerHelper +import com.amity.socialcloud.uikit.community.compose.post.detail.elements.AmityPostMediaVideoPlayer + +@Composable +fun AmityCommunityVideoFeedItem( + modifier: Modifier = Modifier, + data: AmityPost.Data.VIDEO, +) { + val thumbnailUrl = remember(data) { + data.getThumbnailImage()?.getUrl(AmityImage.Size.MEDIUM) + } + val videoData by remember { + data.getVideo() + }.subscribeAsState(initial = null) + + val videoDuration by remember(videoData) { + derivedStateOf { + videoData?.getMetadata() + ?.getAsJsonObject("video") + ?.get("duration") + ?.asNumber + ?: 0 + } + } + + var showPopupDialog by remember { + mutableStateOf(false) + } + + Box( + modifier = modifier + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)) + .background(AmityTheme.colors.baseShade4) + ) { + AsyncImage( + model = ImageRequest + .Builder(LocalContext.current) + .data(thumbnailUrl) + .crossfade(true) + .networkCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .memoryCachePolicy(CachePolicy.ENABLED) + .build(), + contentDescription = "Video Post", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clickableWithoutRipple { + showPopupDialog = true + } + ) + + Text( + text = videoDuration.formatVideoDuration(), + style = AmityTheme.typography.body.copy( + color = Color.White, + ), + modifier = Modifier + .align(Alignment.BottomStart) + .offset(8.dp, (-8).dp) + .background( + color = Color.Black.copy(alpha = 0.7f), + shape = RoundedCornerShape(4.dp) + ) + .padding(horizontal = 4.dp, vertical = 1.dp) + ) + } + + if (showPopupDialog) { + AmityCommunityVideoPreviewDialog(data = data) { + showPopupDialog = false + } + } +} + +@androidx.annotation.OptIn(UnstableApi::class) +@Composable +fun AmityCommunityVideoPreviewDialog( + modifier: Modifier = Modifier, + data: AmityPost.Data.VIDEO, + onDismiss: () -> Unit, +) { + val context = LocalContext.current + val exoPlayer = remember { + ExoPlayer.Builder(context) + .setSeekBackIncrementMs(15_000) + .setSeekForwardIncrementMs(15_000) + .setPauseAtEndOfMediaItems(true) + .build() + } + val video by remember { + data.getVideo() + }.subscribeAsState(initial = null) + + var isAudioMuted by remember { mutableStateOf(false) } + + LaunchedEffect(exoPlayer) { + AmityPostVideoPlayerHelper.setup(exoPlayer) + } + + LaunchedEffect(video) { + video?.let { + AmityPostVideoPlayerHelper.add(it) + AmityPostVideoPlayerHelper.playMediaItem(0) + } + } + + DisposableEffect(Unit) { + onDispose { + exoPlayer.release() + AmityPostVideoPlayerHelper.clear() + } + } + + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties( + usePlatformDefaultWidth = false + ), + ) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.Black) + ) { + AmityPostMediaVideoPlayer( + exoPlayer = exoPlayer, + isVisible = true, + ) + + AmityMenuButton( + icon = R.drawable.amity_ic_close2, + size = 32.dp, + iconPadding = 10.dp, + tint = Color.Black.copy(0.5f), + background = Color.White.copy(0.8f), + modifier = modifier + .align(Alignment.TopStart) + .offset(16.dp, 32.dp), + onClick = { + onDismiss() + } + ) + } + } +} \ No newline at end of file diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/AmityCommunitySettingPage.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/AmityCommunitySettingPage.kt index 2125930a..b17e3063 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/AmityCommunitySettingPage.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/AmityCommunitySettingPage.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -220,6 +221,7 @@ fun AmityCommunitySettingPage( elementId = "edit_profile" ) { AmityCommunitySettingItem( + modifier = modifier.testTag(getAccessibilityId()), title = getConfig().getText(), icon = { Box( @@ -255,6 +257,7 @@ fun AmityCommunitySettingPage( elementId = "members" ) { AmityCommunitySettingItem( + modifier = modifier.testTag(getAccessibilityId()), title = getConfig().getText(), icon = { Box( @@ -297,6 +300,7 @@ fun AmityCommunitySettingPage( elementId = "notifications" ) { AmityCommunitySettingItem( + modifier = modifier.testTag(getAccessibilityId()), title = getConfig().getText(), icon = { Box( @@ -358,6 +362,7 @@ fun AmityCommunitySettingPage( elementId = "post_permission" ) { AmityCommunitySettingItem( + modifier = modifier.testTag(getAccessibilityId()), title = getConfig().getText(), icon = { Box( @@ -394,6 +399,7 @@ fun AmityCommunitySettingPage( elementId = "story_setting" ) { AmityCommunitySettingItem( + modifier = modifier.testTag(getAccessibilityId()), title = getConfig().getText(), icon = { Box( @@ -436,6 +442,7 @@ fun AmityCommunitySettingPage( ), modifier = modifier .padding(vertical = 12.dp, horizontal = 8.dp) + .testTag(getAccessibilityId()) .clickableWithoutRipple { viewModel.updateUIEvent( if (hasDeletePermission) { @@ -470,7 +477,9 @@ fun AmityCommunitySettingPage( color = AmityTheme.colors.alert, fontWeight = FontWeight.SemiBold, ), - modifier = modifier.padding(horizontal = 8.dp) + modifier = modifier + .padding(horizontal = 8.dp) + .testTag(getAccessibilityId()) ) } @@ -486,7 +495,9 @@ fun AmityCommunitySettingPage( fontWeight = FontWeight.Normal, color = AmityTheme.colors.baseShade1, ), - modifier = modifier.padding(horizontal = 8.dp) + modifier = modifier + .padding(horizontal = 8.dp) + .testTag(getAccessibilityId()) ) } diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/elements/AmityCommunitySettingRadioGroup.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/elements/AmityCommunitySettingRadioGroup.kt index b6ae95c8..f2f7e66a 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/elements/AmityCommunitySettingRadioGroup.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setting/elements/AmityCommunitySettingRadioGroup.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme import com.amity.socialcloud.uikit.common.utils.clickableWithoutRipple @@ -52,9 +53,10 @@ fun AmityCommunitySettingRadioGroupItem( modifier = modifier .weight(1f) .padding(start = 16.dp) + .testTag(text) ) RadioButton( - modifier = modifier, + modifier = modifier.testTag(text), selected = isSelected, colors = RadioButtonDefaults.colors( selectedColor = AmityTheme.colors.highlight, diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/AmityCommunitySetupPage.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/AmityCommunitySetupPage.kt index fb04bf9e..ddfd3b80 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/AmityCommunitySetupPage.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/AmityCommunitySetupPage.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -251,6 +252,7 @@ fun AmityCommunitySetupPage( .clickableWithoutRipple { showLeaveConfirmDialog = true } + .testTag(getAccessibilityId()) ) } AmityBaseElement( @@ -263,6 +265,7 @@ fun AmityCommunitySetupPage( modifier = modifier .fillMaxWidth() .padding(vertical = 16.dp) + .testTag(getAccessibilityId()) ) } } @@ -317,7 +320,8 @@ fun AmityCommunitySetupPage( ) { Text( text = getConfig().getText(), - style = AmityTheme.typography.title + style = AmityTheme.typography.title, + modifier = modifier.testTag(getAccessibilityId()) ) } @@ -359,7 +363,8 @@ fun AmityCommunitySetupPage( ) { Text( text = getConfig().getText(), - style = AmityTheme.typography.title + style = AmityTheme.typography.title, + modifier = modifier.testTag(getAccessibilityId()) ) } Text( @@ -399,7 +404,9 @@ fun AmityCommunitySetupPage( Text( text = getConfig().getText(), style = AmityTheme.typography.title, - modifier = modifier.padding(horizontal = 16.dp) + modifier = modifier + .padding(horizontal = 16.dp) + .testTag(getAccessibilityId()) ) } Spacer(modifier = modifier.height(18.dp)) @@ -468,7 +475,9 @@ fun AmityCommunitySetupPage( Text( text = getConfig().getText(), style = AmityTheme.typography.title, - modifier = modifier.padding(horizontal = 16.dp) + modifier = modifier + .padding(horizontal = 16.dp) + .testTag(getAccessibilityId()) ) } Row( @@ -493,6 +502,7 @@ fun AmityCommunitySetupPage( modifier = modifier .size(16.dp) .align(Alignment.Center) + .testTag(getAccessibilityId()) ) } } @@ -511,7 +521,8 @@ fun AmityCommunitySetupPage( text = context.getString(R.string.amity_public), style = AmityTheme.typography.body.copy( fontWeight = FontWeight.SemiBold - ) + ), + modifier = modifier.testTag(getAccessibilityId()) ) } Spacer(modifier.height(2.dp)) @@ -524,7 +535,8 @@ fun AmityCommunitySetupPage( style = AmityTheme.typography.caption.copy( color = AmityTheme.colors.baseShade1, fontWeight = FontWeight.Normal, - ) + ), + modifier = modifier.testTag(getAccessibilityId()) ) } } @@ -561,6 +573,7 @@ fun AmityCommunitySetupPage( modifier = modifier .size(16.dp) .align(Alignment.Center) + .testTag(getAccessibilityId()) ) } } @@ -579,7 +592,8 @@ fun AmityCommunitySetupPage( text = context.getString(R.string.amity_private), style = AmityTheme.typography.body.copy( fontWeight = FontWeight.SemiBold - ) + ), + modifier = modifier.testTag(getAccessibilityId()) ) } Spacer(modifier.height(2.dp)) @@ -592,7 +606,8 @@ fun AmityCommunitySetupPage( style = AmityTheme.typography.caption.copy( color = AmityTheme.colors.baseShade1, fontWeight = FontWeight.Normal, - ) + ), + modifier = modifier.testTag(getAccessibilityId()) ) } } @@ -623,7 +638,9 @@ fun AmityCommunitySetupPage( Text( text = getConfig().getText(), style = AmityTheme.typography.title, - modifier = modifier.padding(horizontal = 16.dp) + modifier = modifier + .padding(horizontal = 16.dp) + .testTag(getAccessibilityId()) ) } Spacer(modifier = modifier.height(16.dp)) @@ -735,6 +752,7 @@ fun AmityCommunitySetupPage( style = AmityTheme.typography.caption.copy( color = Color.White, ), + modifier = modifier.testTag(getAccessibilityId()) ) } } @@ -745,6 +763,7 @@ fun AmityCommunitySetupPage( if (showMediaCameraSelectionSheet) { AmityMediaImageSelectionSheet( modifier = modifier, + pageScope = getPageScope(), ) { type -> showMediaCameraSelectionSheet = false diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/elements/AmityMediaImageSelectionSheet.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/elements/AmityMediaImageSelectionSheet.kt index e081e5bb..588f9aad 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/elements/AmityMediaImageSelectionSheet.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/community/setup/elements/AmityMediaImageSelectionSheet.kt @@ -16,19 +16,27 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.amity.socialcloud.uikit.common.ui.base.AmityBaseElement import com.amity.socialcloud.uikit.common.ui.elements.AmityBottomSheetActionItem +import com.amity.socialcloud.uikit.common.ui.scope.AmityComposePageScope import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme -import com.amity.socialcloud.uikit.community.compose.R +import com.amity.socialcloud.uikit.common.utils.getIcon +import com.amity.socialcloud.uikit.common.utils.getText -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun AmityMediaImageSelectionSheet( modifier: Modifier = Modifier, + pageScope: AmityComposePageScope? = null, onSelect: (AmityMediaImageSelectionType?) -> Unit = {}, ) { val sheetState = rememberModalBottomSheetState() @@ -38,69 +46,83 @@ fun AmityMediaImageSelectionSheet( sheetState = sheetState, containerColor = AmityTheme.colors.background, windowInsets = WindowInsets(bottom = 0.dp), - modifier = modifier, + modifier = modifier.semantics { + testTagsAsResourceId = true + }, ) { Column( modifier = modifier .fillMaxWidth() .padding(horizontal = 8.dp) ) { - AmityBottomSheetActionItem( - icon = { - Box( - modifier = modifier - .clip(CircleShape) - .background( - color = if (isSystemInDarkTheme()) { - AmityTheme.colors.baseShade3 - } else { - AmityTheme.colors.baseShade4 - }, - ) - .size(32.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.amity_ic_post_attachment_camera), - contentDescription = null, - tint = AmityTheme.colors.base, - modifier = Modifier - .size(24.dp) - .align(Alignment.Center), - ) - } - }, - text = "Camera", + AmityBaseElement( + pageScope = pageScope, + elementId = "camera_button" ) { - onSelect(AmityMediaImageSelectionType.CAMERA) + AmityBottomSheetActionItem( + modifier = modifier.testTag(getAccessibilityId()), + icon = { + Box( + modifier = modifier + .clip(CircleShape) + .background( + color = if (isSystemInDarkTheme()) { + AmityTheme.colors.baseShade3 + } else { + AmityTheme.colors.baseShade4 + }, + ) + .size(32.dp) + ) { + Icon( + painter = painterResource(id = getConfig().getIcon()), + contentDescription = null, + tint = AmityTheme.colors.base, + modifier = Modifier + .size(24.dp) + .align(Alignment.Center), + ) + } + }, + text = getConfig().getText(), + ) { + onSelect(AmityMediaImageSelectionType.CAMERA) + } } - AmityBottomSheetActionItem( - icon = { - Box( - modifier = modifier - .clip(CircleShape) - .background( - color = if (isSystemInDarkTheme()) { - AmityTheme.colors.baseShade3 - } else { - AmityTheme.colors.baseShade4 - }, - ) - .size(32.dp) - ) { - Icon( - painter = painterResource(id = R.drawable.amity_ic_post_attachment_photo), - contentDescription = null, - tint = AmityTheme.colors.base, - modifier = Modifier - .size(24.dp) - .align(Alignment.Center), - ) - } - }, - text = "Photo", + AmityBaseElement( + pageScope = pageScope, + elementId = "image_button" ) { - onSelect(AmityMediaImageSelectionType.IMAGE) + AmityBottomSheetActionItem( + modifier = modifier.testTag(getAccessibilityId()), + icon = { + Box( + modifier = modifier + .clip(CircleShape) + .background( + color = if (isSystemInDarkTheme()) { + AmityTheme.colors.baseShade3 + } else { + AmityTheme.colors.baseShade4 + }, + ) + .size(32.dp) + ) { + Icon( + painter = painterResource(id = getConfig().getIcon()), + contentDescription = null, + tint = AmityTheme.colors.base, + modifier = Modifier + .size(24.dp) + .align(Alignment.Center), + ) + } + }, + text = getConfig().getText(), + ) { + onSelect(AmityMediaImageSelectionType.IMAGE) + } } Box(modifier = Modifier.height(16.dp)) diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostDetailPage.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostDetailPage.kt index 84407afd..f567a78d 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostDetailPage.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostDetailPage.kt @@ -4,17 +4,22 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -25,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel @@ -38,6 +44,8 @@ import com.amity.socialcloud.uikit.common.ui.theme.AmityTheme import com.amity.socialcloud.uikit.common.utils.clickableWithoutRipple import com.amity.socialcloud.uikit.common.utils.closePage import com.amity.socialcloud.uikit.common.utils.getIcon +import com.amity.socialcloud.uikit.common.utils.getKeyboardHeight +import com.amity.socialcloud.uikit.common.utils.isKeyboardVisible import com.amity.socialcloud.uikit.community.compose.comment.amityCommentListComponent import com.amity.socialcloud.uikit.community.compose.comment.create.AmityCommentComposerBar import com.amity.socialcloud.uikit.community.compose.post.detail.components.AmityPostContentComponent @@ -73,6 +81,20 @@ fun AmityPostDetailPage( var replyComment by remember { mutableStateOf(null) } + val isKeyboardOpen by isKeyboardVisible() + val keyboardHeight by getKeyboardHeight() + val systemBarPadding = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() + + val commentComposeBarBottomOffset by remember(systemBarPadding) { + derivedStateOf { + if (isKeyboardOpen) { + 2.dp.minus(keyboardHeight).plus(systemBarPadding).coerceAtMost(0.dp) + } else { + 0.dp + } + } + } + AmityBasePage(pageId = "post_detail_page") { AmityBaseComponent( pageScope = getPageScope(), @@ -176,6 +198,7 @@ fun AmityPostDetailPage( } AmityCommentComposerBar( + modifier = modifier.offset(y = commentComposeBarBottomOffset), componentScope = getComponentScope(), referenceId = id, referenceType = AmityCommentReferenceType.POST, diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostVideoPlayerHelper.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostVideoPlayerHelper.kt index ca1993dd..6e6e82f9 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostVideoPlayerHelper.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/AmityPostVideoPlayerHelper.kt @@ -14,6 +14,10 @@ object AmityPostVideoPlayerHelper { } } + fun add(video: AmityVideo) { + add(listOf(video)) + } + fun add(videos: List) { videos.map { video -> exoPlayer?.apply { diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/components/AmityPostContentComponent.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/components/AmityPostContentComponent.kt index 9e41625b..61af6770 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/components/AmityPostContentComponent.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/components/AmityPostContentComponent.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel @@ -126,6 +127,7 @@ fun AmityPostContentComponent( } } .isVisible { isVisible = it } + .testTag(getAccessibilityId()) ) { AmityPostHeaderElement( modifier = modifier, diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostEngagementView.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostEngagementView.kt index 016fa4c6..7a745682 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostEngagementView.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostEngagementView.kt @@ -163,7 +163,8 @@ fun AmityPostEngagementView( postId = post.getPostId(), isReacted = isReacted ) - }, + } + .testTag(getAccessibilityId()), ) Text( text = if (isPostDetailPage) getConfig().getText() @@ -173,7 +174,6 @@ fun AmityPostEngagementView( color = if (isReacted) AmityTheme.colors.primary else AmityTheme.colors.baseShade2 ), - modifier = modifier.testTag(getAccessibilityId()), ) } @@ -185,7 +185,9 @@ fun AmityPostEngagementView( Image( painter = painterResource(id = getConfig().getIcon()), contentDescription = null, - modifier = modifier.size(20.dp) + modifier = modifier + .size(20.dp) + .testTag(getAccessibilityId()) ) Text( text = if (isPostDetailPage) getConfig().getText() @@ -194,7 +196,6 @@ fun AmityPostEngagementView( fontWeight = FontWeight.SemiBold, color = AmityTheme.colors.baseShade2 ), - modifier = modifier.testTag(getAccessibilityId()), ) } } diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostMediaPreviewDialog.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostMediaPreviewDialog.kt index 45dd3a12..10ceacda 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostMediaPreviewDialog.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/post/detail/elements/AmityPostMediaPreviewDialog.kt @@ -65,6 +65,7 @@ fun AmityPostMediaPreviewDialog( ExoPlayer.Builder(context) .setSeekBackIncrementMs(15_000) .setSeekForwardIncrementMs(15_000) + .setPauseAtEndOfMediaItems(true) .build() } var isAudioMuted by remember { mutableStateOf(false) } diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityCreatePostMenuComponent.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityCreatePostMenuComponent.kt index 57c21fce..0be03153 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityCreatePostMenuComponent.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityCreatePostMenuComponent.kt @@ -16,11 +16,14 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp @@ -37,6 +40,7 @@ import com.amity.socialcloud.uikit.community.compose.AmitySocialBehaviorHelper import com.amity.socialcloud.uikit.community.compose.post.composer.AmityPostTargetType import com.amity.socialcloud.uikit.community.compose.target.AmityPostTargetSelectionPageType +@OptIn(ExperimentalComposeUiApi::class) @Composable fun AmityCreatePostMenuComponent( modifier: Modifier = Modifier, @@ -57,7 +61,7 @@ fun AmityCreatePostMenuComponent( val targetStoryBehavior by lazy { AmitySocialBehaviorHelper.storyTargetSelectionPageBehavior } - + val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { @@ -76,6 +80,9 @@ fun AmityCreatePostMenuComponent( .width(180.dp) .background(AmityTheme.colors.background) .clip(RoundedCornerShape(12.dp)) + .semantics { + testTagsAsResourceId = true + } ) { DropdownMenuItem( text = { diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityGlobalFeedComponent.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityGlobalFeedComponent.kt index 54fab7ac..81e6f3ab 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityGlobalFeedComponent.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityGlobalFeedComponent.kt @@ -68,7 +68,7 @@ fun AmityGlobalFeedComponent( AmityBaseComponent( pageScope = pageScope, - componentId = "global_feed_component" + componentId = "global_feed" ) { Box( modifier = Modifier @@ -96,11 +96,10 @@ fun AmityGlobalFeedComponent( items( count = AmityPostComposerHelper.getCreatedPosts().size, - key = { AmityPostComposerHelper.getCreatedPosts()[it].getPostId() } + key = { "temp_" + AmityPostComposerHelper.getCreatedPosts()[it].getPostId() } ) { val post = AmityPostComposerHelper.getCreatedPosts()[it] AmityPostContentComponent( - modifier = modifier, post = post, style = AmityPostContentComponentStyle.FEED, hideMenuButton = false, @@ -135,7 +134,6 @@ fun AmityGlobalFeedComponent( } AmityPostContentComponent( - modifier = modifier, post = post, style = AmityPostContentComponentStyle.FEED, hideMenuButton = false, @@ -158,7 +156,10 @@ fun AmityGlobalFeedComponent( AmityNewsFeedDivider() } - else -> {} + else -> { + AmityPostShimmer() + AmityNewsFeedDivider() + } } } } diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityNewsFeedComponent.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityNewsFeedComponent.kt index 94d7e05e..aaf7e07f 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityNewsFeedComponent.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/socialhome/components/AmityNewsFeedComponent.kt @@ -2,6 +2,7 @@ package com.amity.socialcloud.uikit.community.compose.socialhome.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import com.amity.socialcloud.uikit.common.ui.base.AmityBaseComponent import com.amity.socialcloud.uikit.common.ui.scope.AmityComposePageScope @@ -12,10 +13,10 @@ fun AmityNewsFeedComponent( ) { AmityBaseComponent( pageScope = pageScope, - componentId = "newsfeed_component" + componentId = "newsfeed" ) { AmityGlobalFeedComponent( - modifier = modifier, + modifier = modifier.testTag(getAccessibilityId()), pageScope = pageScope ) } diff --git a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/story/create/AmityCreateStoryPage.kt b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/story/create/AmityCreateStoryPage.kt index 3eeb5fc7..4af1f3c9 100644 --- a/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/story/create/AmityCreateStoryPage.kt +++ b/social-compose/src/main/java/com/amity/socialcloud/uikit/community/compose/story/create/AmityCreateStoryPage.kt @@ -272,23 +272,13 @@ fun AmityCreateStoryPage( .testTag("media_picker_button"), ) { haptics.performHapticFeedback(HapticFeedbackType.LongPress) - if (isMediaPermissionGranted) { - mediaPickerLauncher.launch( - PickVisualMediaRequest( - mediaType = - if (isPhotoSelected) ActivityResultContracts.PickVisualMedia.ImageOnly - else ActivityResultContracts.PickVisualMedia.VideoOnly, - ) + mediaPickerLauncher.launch( + PickVisualMediaRequest( + mediaType = + if (isPhotoSelected) ActivityResultContracts.PickVisualMedia.ImageOnly + else ActivityResultContracts.PickVisualMedia.VideoOnly, ) - } else { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - mediaPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - mediaPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES) - } else { - mediaPermissionLauncher.launch(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) - } - } + ) } AmityMenuButton( icon = R.drawable.amity_ic_story_switch_camera, diff --git a/social-compose/src/main/res/drawable/amity_ic_photo_empty.xml b/social-compose/src/main/res/drawable/amity_ic_photo_empty.xml new file mode 100644 index 00000000..44559e7d --- /dev/null +++ b/social-compose/src/main/res/drawable/amity_ic_photo_empty.xml @@ -0,0 +1,9 @@ + + + diff --git a/social-compose/src/main/res/drawable/amity_ic_video_empty.xml b/social-compose/src/main/res/drawable/amity_ic_video_empty.xml new file mode 100644 index 00000000..f4872353 --- /dev/null +++ b/social-compose/src/main/res/drawable/amity_ic_video_empty.xml @@ -0,0 +1,9 @@ + + + diff --git a/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityBaseCreatePostFragment.kt b/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityBaseCreatePostFragment.kt index ae9244df..234586e8 100644 --- a/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityBaseCreatePostFragment.kt +++ b/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityBaseCreatePostFragment.kt @@ -3,7 +3,6 @@ package com.amity.socialcloud.uikit.community.newsfeed.fragment import android.Manifest import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.net.Uri import android.os.Build @@ -20,6 +19,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.fragment.app.activityViewModels @@ -46,8 +46,6 @@ import com.amity.socialcloud.uikit.common.utils.AmityOptionMenuColorUtil import com.amity.socialcloud.uikit.community.R import com.amity.socialcloud.uikit.community.databinding.AmityFragmentPostCreateBinding import com.amity.socialcloud.uikit.community.domain.model.AmityFileAttachment -import com.amity.socialcloud.uikit.common.base.AmityImagePickerActivity -import com.amity.socialcloud.uikit.common.base.AmityVideoPickerActivity import com.amity.socialcloud.uikit.community.newsfeed.adapter.AmityCreatePostFileAdapter import com.amity.socialcloud.uikit.community.newsfeed.adapter.AmityCreatePostMediaAdapter import com.amity.socialcloud.uikit.community.newsfeed.adapter.AmityPostAttachmentOptionsAdapter @@ -68,9 +66,6 @@ import com.google.android.material.snackbar.Snackbar import com.linkedin.android.spyglass.suggestions.interfaces.SuggestionsVisibilityManager import com.linkedin.android.spyglass.tokenization.QueryToken import com.linkedin.android.spyglass.tokenization.interfaces.QueryTokenReceiver -import com.zhihu.matisse.Matisse -import com.zhihu.matisse.MimeType -import com.zhihu.matisse.engine.impl.GlideEngine import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable @@ -115,8 +110,8 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), private var fileAdapter: AmityCreatePostFileAdapter? = null private val userMentionAdapter by lazy { AmityUserMentionAdapter() } private val userMentionPagingDataAdapter by lazy { AmityUserMentionPagingDataAdapter() } - private lateinit var imagePickerLauncher: ActivityResultLauncher - private lateinit var videoPickerLauncher: ActivityResultLauncher + private lateinit var imagePickerLauncher: ActivityResultLauncher + private lateinit var videoPickerLauncher: ActivityResultLauncher private val searchDisposable: CompositeDisposable by lazy { CompositeDisposable() @@ -174,22 +169,23 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), } return super.onOptionsItemSelected(item) } - + private fun registerMediaPickerResult() { - imagePickerLauncher = requireActivity().registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.let { - addMedia(it, PostMedia.Type.IMAGE, true) - } + imagePickerLauncher = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia( + MAX_IMAGE_SELECTABLE)) { uris -> + val selectedCount = viewModel.getImages().value?.size ?: 0 + if(uris.isNotEmpty()) { + val canSelect = Math.min(MAX_IMAGE_SELECTABLE - selectedCount, uris.size) + addMedia(uris.subList(0, canSelect), PostMedia.Type.IMAGE) } } - videoPickerLauncher = requireActivity().registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.let { - addMedia(it, PostMedia.Type.VIDEO, true) - } + + videoPickerLauncher = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia( + MAX_VIDEO_SELECTABLE)) { uris -> + val selectedCount = viewModel.getImages().value?.size ?: 0 + if(uris.isNotEmpty()) { + val canSelect = Math.min(MAX_VIDEO_SELECTABLE - selectedCount, uris.size) + addMedia(uris.subList(0, canSelect), PostMedia.Type.VIDEO) } } } @@ -525,7 +521,7 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), if (hasReachedSelectionLimit()) { view?.showSnackBar(getString(R.string.amity_create_post_max_image_selected_warning)) } else { - grantStoragePermission(REQUEST_STORAGE_PERMISSION_IMAGE_UPLOAD) { openImagePicker() } + openImagePicker() } } @@ -533,7 +529,7 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), if (hasReachedSelectionLimit()) { view?.showSnackBar(getString(R.string.amity_create_post_max_image_selected_warning)) } else { - grantStoragePermission(REQUEST_STORAGE_PERMISSION_VIDEO_UPLOAD) { openVideoPicker() } + openVideoPicker() } } @@ -547,23 +543,8 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), private fun grantStoragePermission(requestCode: Int, onPermissionGrant: () -> Unit) { val requiredPermissions = emptyList().toMutableList() - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - requiredPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - when (requestCode) { - REQUEST_STORAGE_PERMISSION_IMAGE_UPLOAD -> requiredPermissions.add(Manifest.permission.READ_MEDIA_IMAGES) - REQUEST_STORAGE_PERMISSION_VIDEO_UPLOAD -> requiredPermissions.add(Manifest.permission.READ_MEDIA_VIDEO) - } - } else { - when (requestCode) { - REQUEST_STORAGE_PERMISSION_IMAGE_UPLOAD -> { - requiredPermissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) - } - REQUEST_STORAGE_PERMISSION_VIDEO_UPLOAD -> { - requiredPermissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) - } - } - } + requiredPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE) + val hasRequiredPermission = requiredPermissions.fold(true) { acc, permission -> acc && hasPermission(permission) } @@ -761,26 +742,8 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), } private fun openImagePicker() { - val selectedImageCount = getSelectedImageCount() if (canSelectImage()) { - val isSupportPhotoPicker = ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext()) - if (isSupportPhotoPicker && ::imagePickerLauncher.isInitialized) { - val intent = AmityImagePickerActivity.newIntent( - context = requireContext(), - maxItems = MAX_IMAGE_SELECTABLE - selectedImageCount - ) - imagePickerLauncher.launch(intent) - } else { - Matisse.from(this) - .choose(MimeType.of(MimeType.JPEG, MimeType.PNG, MimeType.GIF)) - .showSingleMediaType(true) - .countable(true) - .maxSelectable(MAX_IMAGE_SELECTABLE - selectedImageCount) - .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - .imageEngine(GlideEngine()) - .theme(R.style.AmityImagePickerTheme) - .forResult(AmityConstants.PICK_IMAGES) - } + imagePickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } } @@ -804,24 +767,7 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), if (selectedVideoCount == MAX_VIDEO_SELECTABLE) { view?.showSnackBar(getString(R.string.amity_create_post_max_image_selected_warning)) } else { - val isSupportPhotoPicker = ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext()) - if (isSupportPhotoPicker && ::videoPickerLauncher.isInitialized) { - val intent = AmityVideoPickerActivity.newIntent( - context = requireContext(), - maxItems = MAX_VIDEO_SELECTABLE - selectedVideoCount, - ) - videoPickerLauncher.launch(intent) - } else { - Matisse.from(this) - .choose(MimeType.ofVideo()) - .showSingleMediaType(true) - .countable(true) - .maxSelectable(MAX_VIDEO_SELECTABLE - selectedVideoCount) - .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - .imageEngine(GlideEngine()) - .theme(R.style.AmityImagePickerTheme) - .forResult(AmityConstants.PICK_VIDEOS) - } + videoPickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)) } } @@ -846,17 +792,6 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) when (requestCode) { - AmityConstants.PICK_IMAGES -> { - data?.let { - addMedia(it, PostMedia.Type.IMAGE) - } - } - - AmityConstants.PICK_VIDEOS -> { - data?.let { - addMedia(it, PostMedia.Type.VIDEO) - } - } AmityConstants.PICK_FILES -> { if (data != null) @@ -968,14 +903,10 @@ abstract class AmityBaseCreatePostFragment : AmityBaseFragment(), || (currentAttachmentCount + 1) > MAX_FILE_SELECTABLE } - private fun addMedia(it: Intent, mediaType: PostMedia.Type, isNative: Boolean = false) { + private fun addMedia(uris: List, mediaType: PostMedia.Type) { setupImageAdapter() - if (isNative) { - it.let(AmityImagePickerActivity::getUris)?.toList() - } else { - Matisse.obtainResult(it) - }?.let { resultUris -> - val postMediaList = viewModel.addMedia(resultUris, mediaType) + if(uris.isNotEmpty()) { + val postMediaList = viewModel.addMedia(uris, mediaType) uploadMedia(postMediaList) } } diff --git a/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityLiveStreamPostCreatorFragment.kt b/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityLiveStreamPostCreatorFragment.kt index 95551af0..7d5c0eb2 100644 --- a/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityLiveStreamPostCreatorFragment.kt +++ b/social/src/main/java/com/amity/socialcloud/uikit/community/newsfeed/fragment/AmityLiveStreamPostCreatorFragment.kt @@ -4,7 +4,6 @@ import android.Manifest import android.app.Activity import android.content.DialogInterface import android.content.Intent -import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri @@ -14,6 +13,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels @@ -25,12 +25,10 @@ import com.amity.socialcloud.sdk.video.AmityStreamBroadcasterConfiguration import com.amity.socialcloud.sdk.video.StreamBroadcaster import com.amity.socialcloud.sdk.video.model.AmityBroadcastResolution import com.amity.socialcloud.sdk.video.model.AmityStreamBroadcasterState -import com.amity.socialcloud.uikit.common.base.AmityImagePickerActivity import com.amity.socialcloud.uikit.common.common.showSnackBar import com.amity.socialcloud.uikit.common.common.views.dialog.bottomsheet.AmityBottomSheetDialog import com.amity.socialcloud.uikit.common.common.views.dialog.bottomsheet.BottomSheetMenuItem import com.amity.socialcloud.uikit.common.utils.AmityAlertDialogUtil -import com.amity.socialcloud.uikit.common.utils.AmityConstants import com.amity.socialcloud.uikit.community.R import com.amity.socialcloud.uikit.community.databinding.AmityFragmentLiveStreamPostCreatorBinding import com.amity.socialcloud.uikit.community.newsfeed.adapter.AmityUserMentionAdapter @@ -45,9 +43,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.linkedin.android.spyglass.suggestions.interfaces.SuggestionsVisibilityManager import com.linkedin.android.spyglass.tokenization.QueryToken import com.trello.rxlifecycle4.components.support.RxFragment -import com.zhihu.matisse.Matisse -import com.zhihu.matisse.MimeType -import com.zhihu.matisse.engine.impl.GlideEngine import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable @@ -55,6 +50,7 @@ import io.reactivex.rxjava3.internal.operators.flowable.FlowableInterval import io.reactivex.rxjava3.schedulers.Schedulers import java.util.concurrent.TimeUnit + private const val REQUEST_LIVE_STREAM_CAMERA_PERMISSIONS = 20001 private const val REQUEST_LIVE_STREAM_STORAGE_PERMISSIONS = 20002 @@ -75,7 +71,6 @@ class AmityLiveStreamPostCreatorFragment : RxFragment() { private var communityId: String? = null private var duration = 0L - private lateinit var imagePickerLauncher: ActivityResultLauncher private var streamBroadcasterState: AmityStreamBroadcasterState = AmityStreamBroadcasterState.IDLE() @@ -84,7 +79,7 @@ class AmityLiveStreamPostCreatorFragment : RxFragment() { private val searchDisposable: CompositeDisposable by lazy { CompositeDisposable() } - + private lateinit var imagePickerLauncher: ActivityResultLauncher override fun onCreateView( inflater: LayoutInflater, @@ -133,17 +128,11 @@ class AmityLiveStreamPostCreatorFragment : RxFragment() { requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), requestCode) } } - + private fun registerImagePickerResult() { - imagePickerLauncher = requireActivity().registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.let { - val uris = it.let(AmityImagePickerActivity::getUris)?.toList() - if (!uris.isNullOrEmpty()) { - uploadThumbnail(uris.first()) - } - } + imagePickerLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + if (uri != null) { + uploadThumbnail(uri) } } } @@ -391,24 +380,7 @@ class AmityLiveStreamPostCreatorFragment : RxFragment() { } private fun openImagePicker() { - val isSupportPhotoPicker = ActivityResultContracts.PickVisualMedia.isPhotoPickerAvailable(requireContext()) - if (isSupportPhotoPicker && ::imagePickerLauncher.isInitialized) { - val intent = AmityImagePickerActivity.newIntent( - context = requireContext(), - maxItems = 1, - ) - imagePickerLauncher.launch(intent) - } else { - Matisse.from(this) - .choose(MimeType.of(MimeType.JPEG, MimeType.PNG, MimeType.GIF)) - .showSingleMediaType(true) - .countable(true) - .maxSelectable(1) - .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) - .imageEngine(GlideEngine()) - .theme(R.style.AmityImagePickerTheme) - .forResult(AmityConstants.PICK_IMAGES) - } + imagePickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } private fun uploadThumbnail(uri: Uri) { @@ -597,18 +569,6 @@ class AmityLiveStreamPostCreatorFragment : RxFragment() { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == Activity.RESULT_OK) when (requestCode) { - AmityConstants.PICK_IMAGES -> { - val results = Matisse.obtainResult(data) - if (!results.isNullOrEmpty()) { - uploadThumbnail(results[0]) - } - } - } - } - internal enum class COMPOSE { DESCRIPTION } class Builder internal constructor() { diff --git a/social/src/main/java/com/amity/socialcloud/uikit/community/ui/view/AmityCommunityCreateBaseFragment.kt b/social/src/main/java/com/amity/socialcloud/uikit/community/ui/view/AmityCommunityCreateBaseFragment.kt index 6ae6cdd1..7cfcc044 100644 --- a/social/src/main/java/com/amity/socialcloud/uikit/community/ui/view/AmityCommunityCreateBaseFragment.kt +++ b/social/src/main/java/com/amity/socialcloud/uikit/community/ui/view/AmityCommunityCreateBaseFragment.kt @@ -1,27 +1,22 @@ package com.amity.socialcloud.uikit.community.ui.view -import android.Manifest import android.content.DialogInterface import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import com.amity.socialcloud.sdk.model.core.error.AmityException import com.amity.socialcloud.sdk.model.core.file.upload.AmityUploadResult import com.amity.socialcloud.uikit.common.common.showSnackBar -import com.amity.socialcloud.uikit.common.contract.AmityPickImageContract import com.amity.socialcloud.uikit.common.utils.AmityAlertDialogUtil import com.amity.socialcloud.uikit.common.utils.AmityConstants -import com.amity.socialcloud.uikit.community.R import com.amity.socialcloud.uikit.community.compose.community.profile.AmityCommunityProfilePageActivity import com.amity.socialcloud.uikit.community.data.AmitySelectCategoryItem import com.amity.socialcloud.uikit.community.databinding.AmityFragmentCreateCommunityBinding @@ -35,6 +30,10 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers import timber.log.Timber +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import com.amity.socialcloud.uikit.community.R abstract class AmityCommunityCreateBaseFragment : RxFragment() { @@ -44,25 +43,7 @@ abstract class AmityCommunityCreateBaseFragment : RxFragment() { lateinit var viewModel: AmityCreateCommunityViewModel internal lateinit var binding: AmityFragmentCreateCommunityBinding - private val pickImage = registerForActivityResult(AmityPickImageContract()) { data -> - if (data != null) { - imageUri = data - viewModel.initialStateChanged.set(true) - Glide.with(this) - .load(data) - .centerCrop() - .into(binding.ccAvatar) - } - } - - private val pickImagePermission = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { - if (it) { - pickImage.launch(getString(com.amity.socialcloud.uikit.common.R.string.amity_choose_image)) - } else { - binding.root.showSnackBar("Permission denied", Snackbar.LENGTH_SHORT) - } - } + private lateinit var imagePickerLauncher: ActivityResultLauncher override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -95,6 +76,16 @@ abstract class AmityCommunityCreateBaseFragment : RxFragment() { setUpBackPress() setAvatar() uploadImageAndCreateCommunity() + imagePickerLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + if(uri != null) { + imageUri = uri + viewModel.initialStateChanged.set(true) + Glide.with(this) + .load(imageUri) + .centerCrop() + .into(binding.ccAvatar) + } + } } private fun uploadImageAndCreateCommunity() { @@ -114,13 +105,7 @@ abstract class AmityCommunityCreateBaseFragment : RxFragment() { fun getBindingVariable(): AmityFragmentCreateCommunityBinding = binding private fun pickImage() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - pickImagePermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE) - } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - pickImagePermission.launch(Manifest.permission.READ_MEDIA_IMAGES) - } else { - pickImagePermission.launch(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) - } + imagePickerLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } private fun launchCategorySelection(preSelectedCategoryAmity: AmitySelectCategoryItem) {