Merge branch 'master' into split-abi-apks

This commit is contained in:
Ammar Githam 2021-05-09 13:32:11 +09:00
commit b56f3e69e2
315 changed files with 11010 additions and 5593 deletions

View File

@ -32,6 +32,24 @@
"question"
]
},
{
"login": "zerrium",
"name": "Zerrium",
"avatar_url": "https://avatars.githubusercontent.com/u/58355441?v=4",
"profile": "https://github.com/zerrium",
"contributions": [
"code"
]
},
{
"login": "junhuicoding",
"name": "Chua Jun Hui",
"avatar_url": "https://avatars.githubusercontent.com/u/54289027?v=4",
"profile": "https://github.com/junhuicoding",
"contributions": [
"code"
]
},
{
"login": "andersonvom",
"name": "Anderson Mesquita",
@ -42,6 +60,16 @@
"bug"
]
},
{
"login": "vojta-horanek",
"name": "Vojtěch Hořánek",
"avatar_url": "https://avatars.githubusercontent.com/u/12630566?v=4",
"profile": "https://vojtechh.eu/",
"contributions": [
"code",
"translation"
]
},
{
"login": "MeLlamoPablo",
"name": "Pablo Rodríguez",
@ -255,15 +283,15 @@
"translation"
]
},
{
"login": "Pyrobauve",
"name": "Pyrobauve",
"avatar_url": "https://avatars.githubusercontent.com/u/48654473?v=4",
"profile": "https://github.com/Pyrobauve",
"contributions": [
"translation"
]
},
{
"login": "Pyrobauve",
"name": "Pyrobauve",
"avatar_url": "https://avatars.githubusercontent.com/u/48654473?v=4",
"profile": "https://github.com/Pyrobauve",
"contributions": [
"translation"
]
},
{
"login": "RAMAR-RAR",
"name": "RAMAR-RAR",

5
.codebeatsettings Normal file
View File

@ -0,0 +1,5 @@
{
"JAVA": {
"TOO_MANY_IVARS": [8, 10, 20, 30]
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -7,7 +7,6 @@
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@ -40,7 +40,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -3,6 +3,7 @@
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>InstaGrabber</name>
<name>Barinsta</name>
<comment>Project instagrabber created by Buildship.</comment>
<projects>
</projects>
@ -16,7 +16,7 @@
</natures>
<filteredResources>
<filter>
<id>1600117114918</id>
<id>0</id>
<name></name>
<type>30</type>
<matcher>

View File

@ -9,7 +9,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Instagram client; previously known as InstaGrabber.
@ -56,52 +56,55 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
<tr>
<td align="center"><a href="https://austinhuang.me"><img src="https://avatars1.githubusercontent.com/u/16656689?s=100" width="100px;" alt=""/><br /><sub><b>Austin Huang</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Documentation">📖</a> <a href="#question-austinhuang0131" title="Answering Questions">💬</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a> <a href="#ideas-austinhuang0131" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/ammargitham"><img src="https://avatars0.githubusercontent.com/u/8017365?s=100" width="100px;" alt=""/><br /><sub><b>Ammar Githam</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=ammargitham" title="Code">💻</a> <a href="#design-ammargitham" title="Design">🎨</a> <a href="#ideas-ammargitham" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-ammargitham" title="Maintenance">🚧</a> <a href="#question-ammargitham" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/zerrium"><img src="https://avatars.githubusercontent.com/u/58355441?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zerrium</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=zerrium" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/junhuicoding"><img src="https://avatars.githubusercontent.com/u/54289027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chua Jun Hui</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=junhuicoding" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/andersonvom"><img src="https://avatars3.githubusercontent.com/u/69922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anderson Mesquita</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aandersonvom" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://vojtechh.eu/"><img src="https://avatars.githubusercontent.com/u/12630566?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vojtěch Hořánek</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=vojta-horanek" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td>
<td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>

View File

@ -20,8 +20,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode 60
versionName '19.1.0'
versionCode 62
versionName '19.2.1'
multiDexEnabled true
@ -33,6 +33,12 @@ android {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
compileOptions {
@ -133,6 +139,13 @@ android {
outputFileName = abi == null ? "barinsta_${suffix}.apk" : "barinsta_${suffix}_${abi}.apk"
}
}
packagingOptions {
// Exclude file to avoid
// Error: Duplicate files during packaging of APK
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/LICENSE-notice.md'
}
}
configurations.all {
@ -143,8 +156,8 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
def appcompat_version = "1.2.0"
def nav_version = '2.3.4'
def exoplayer_version = '2.13.2'
def nav_version = '2.3.5'
def exoplayer_version = '2.13.3'
implementation 'com.google.android.material:material:1.4.0-alpha02'
@ -154,7 +167,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
@ -174,18 +187,16 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX
def camerax_version = "1.1.0-alpha03"
def camerax_version = "1.1.0-alpha04"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha22"
implementation "androidx.camera:camera-view:1.0.0-alpha24"
// EmojiCompat
def emoji_compat_version = "1.1.0"
implementation "androidx.emoji:emoji:$emoji_compat_version"
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT'
implementation 'com.facebook.fresco:fresco:2.3.0'
implementation 'com.facebook.fresco:animated-webp:2.3.0'
implementation 'com.facebook.fresco:webpsupport:2.3.0'
@ -195,6 +206,10 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
implementation 'com.github.skydoves:balloon:1.3.4'
implementation 'com.github.ammargitham:AutoLinkTextViewV2:v3.1.0'
implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2'
implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'
@ -203,4 +218,11 @@ dependencies {
githubImplementation 'io.sentry:sentry-android:4.3.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation "androidx.room:room-testing:2.2.6"
}

View File

@ -0,0 +1,227 @@
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "232e618b3bfcb4661336b359d036c455",
"entities": [
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "cookie",
"columnName": "cookie",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "fullName",
"columnName": "full_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "profilePic",
"columnName": "profile_pic",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorites",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "displayName",
"columnName": "display_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "picUrl",
"columnName": "pic_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "dateAdded",
"columnName": "date_added",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "dm_last_notified",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "threadId",
"columnName": "thread_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastNotifiedMsgTs",
"columnName": "last_notified_msg_ts",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastNotifiedAt",
"columnName": "last_notified_at",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_dm_last_notified_thread_id",
"unique": true,
"columnNames": [
"thread_id"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "recent_searches",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ig_id` TEXT NOT NULL, `name` TEXT NOT NULL, `username` TEXT, `pic_url` TEXT, `type` TEXT NOT NULL, `last_searched_on` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "igId",
"columnName": "ig_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "picUrl",
"columnName": "pic_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSearchedOn",
"columnName": "last_searched_on",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_recent_searches_ig_id_type",
"unique": true,
"columnNames": [
"ig_id",
"type"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `${TABLE_NAME}` (`ig_id`, `type`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '232e618b3bfcb4661336b359d036c455')"
]
}
}

View File

@ -0,0 +1,51 @@
package awais.instagrabber.db;
import androidx.room.Room;
import androidx.room.migration.Migration;
import androidx.room.testing.MigrationTestHelper;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import static awais.instagrabber.db.AppDatabase.MIGRATION_4_5;
import static awais.instagrabber.db.AppDatabase.MIGRATION_5_6;
@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_4_5, MIGRATION_5_6};
@Rule
public MigrationTestHelper helper;
public MigrationTest() {
final String canonicalName = AppDatabase.class.getCanonicalName();
assert canonicalName != null;
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
canonicalName,
new FrameworkSQLiteOpenHelperFactory());
}
@Test
public void migrateAll() throws IOException {
// Create earliest version of the database. Have to start with 4 since that is the version we migrated to Room.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 4);
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
AppDatabase appDb = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().getTargetContext(),
AppDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build();
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
}

View File

@ -0,0 +1,82 @@
package awais.instagrabber.db.dao;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import java.time.LocalDateTime;
import java.util.List;
import awais.instagrabber.db.AppDatabase;
import awais.instagrabber.db.entities.RecentSearch;
import awais.instagrabber.models.enums.FavoriteType;
@RunWith(AndroidJUnit4.class)
public class RecentSearchDaoTest {
private static final String TAG = RecentSearchDaoTest.class.getSimpleName();
private RecentSearchDao dao;
private AppDatabase db;
@Before
public void createDb() {
final Context context = ApplicationProvider.getApplicationContext();
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
dao = db.recentSearchDao();
}
@After
public void closeDb() {
db.close();
}
@Test
public void writeQueryDelete() {
final RecentSearch recentSearch = insertRecentSearch("1", "test1", FavoriteType.HASHTAG);
final RecentSearch byIgIdAndType = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
Assertions.assertEquals(recentSearch, byIgIdAndType);
dao.deleteRecentSearch(byIgIdAndType);
final RecentSearch deleted = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
Assertions.assertNull(deleted);
}
@Test
public void queryAllOrdered() {
final List<RecentSearch> insertListReversed = ImmutableList
.<RecentSearch>builder()
.add(insertRecentSearch("1", "test1", FavoriteType.HASHTAG))
.add(insertRecentSearch("2", "test2", FavoriteType.LOCATION))
.add(insertRecentSearch("3", "test3", FavoriteType.USER))
.add(insertRecentSearch("4", "test4", FavoriteType.USER))
.add(insertRecentSearch("5", "test5", FavoriteType.USER))
.build()
.reverse(); // important
final List<RecentSearch> fromDb = dao.getAllRecentSearches();
Assertions.assertIterableEquals(insertListReversed, fromDb);
}
@NonNull
private RecentSearch insertRecentSearch(final String igId, final String name, final FavoriteType type) {
final RecentSearch recentSearch = new RecentSearch(
igId,
name,
null,
null,
type,
LocalDateTime.now()
);
dao.insertRecentSearch(recentSearch);
return recentSearch;
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Habilita el Sentry</string>
<string name="sentry_summary">Sentry és un oient/intèrpret d\'error que envia asíncronament l\'error/esdeveniment a Sentry.io</string>
<string name="sentry_start_next_launch">Sentry s\'iniciarà al pròxim llançament</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Povolit Sentry</string>
<string name="sentry_summary">Sentry je listener/handler, který zaznamenává chyby a asynchronně je posílá na Sentry.io</string>
<string name="sentry_start_next_launch">Sentry se spustí při příštím spuštění</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Ενεργοποίηση Sentry</string>
<string name="sentry_summary">Το Sentry είναι διαχειριστής σφαλμάτων ασύγχρονης αποστολής του σφάλματος/συμβάντος στο Sentry.io</string>
<string name="sentry_start_next_launch">Το Sentry θα ξεκινήσει στην επόμενη εκκίνηση</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Activar Sentry</string>
<string name="sentry_summary">Sentry es un oyente/manejador de errores que asincrónicamente envía el error/evento a Sentry.io</string>
<string name="sentry_start_next_launch">Sentry comenzará en el próximo inicio</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Activer Sentry</string>
<string name="sentry_summary">Sentry est un écouteur/gestionnaire d\'erreurs qui envoie de manière asynchrone l\'erreur/l\'événement à Sentry.io</string>
<string name="sentry_start_next_launch">Sentry commencera au prochain lancement</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Abilita Sentry</string>
<string name="sentry_summary">Sentry è un ascoltatore/gestore di errori che invia asincronicamente l\'errore/evento a Sentry.io</string>
<string name="sentry_start_next_launch">Sentry comincerà al prossimo lancio</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Włącz Sentry</string>
<string name="sentry_summary">Sentry jest słuchaczem/obsługą błędów, które asynchronicznie wysyłają błąd/zdarzenie do Sentry.io</string>
<string name="sentry_start_next_launch">Sentry rozpocznie się przy następnym uruchomieniu</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Ativar Sentry</string>
<string name="sentry_summary">Sentry é um ouvinte/gestor de erros que assincronicamente envia o erro/evento para Sentry.io</string>
<string name="sentry_start_next_launch">Sentry começará no próximo início</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Включить режим \"часового\"</string>
<string name="sentry_summary">\"Часовой\" - это слушатель/обработчик ошибок, который асинхронно отправляет ошибку/событие на Sentry.io</string>
<string name="sentry_start_next_launch">\"Часовой\" будет запущен при следующем запуске</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">Enable Sentry</string>
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">启用 Sentry</string>
<string name="sentry_summary">Sentry 会将错误报告发送至 Sentry.io</string>
<string name="sentry_start_next_launch">启用 Sentry 将在下次启动应用时生效</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="enable_sentry">啟用 Sentry</string>
<string name="sentry_summary">Sentry 將會錯誤報告發送至 Sentry.io</string>
<string name="sentry_start_next_launch">下次啟用應用程式時將會開啟 Sentry</string>
</resources>

View File

@ -54,6 +54,8 @@
<data android:scheme="https" />
<data android:host="ig.me" />
<data android:host="www.ig.me" />
<data android:host="instagr.am" />
<data android:host="www.instagr.am" />
<data android:host="instagram.com" />
<data android:host="www.instagram.com" />
<data android:pathPrefix="/" />
@ -130,6 +132,9 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />
</activity>
<activity
android:name=".utils.ProcessPhoenix"
android:theme="@style/Theme.AppCompat.Translucent" />
<provider
android:name="androidx.core.content.FileProvider"
@ -170,4 +175,4 @@
android:value="Noto Color Emoji Compat" />
</application>
</manifest>
</manifest>

View File

@ -26,6 +26,9 @@ import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public final class InstaGrabberApplication extends Application {
private static final String TAG = "InstaGrabberApplication";
@ -52,7 +55,7 @@ public final class InstaGrabberApplication extends Application {
Log.e(TAG, "Error", e);
}
}
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig

View File

@ -24,7 +24,7 @@ import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
public final class Login extends BaseLanguageActivity implements View.OnClickListener {
private final WebViewClient webViewClient = new WebViewClient() {
@Override
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
@ -53,7 +53,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
}
private final WebChromeClient webChromeClient = new WebChromeClient();
private String webViewUrl, defaultUserAgent;
private String webViewUrl;
private boolean ready = false;
private ActivityLoginBinding loginBinding;
@ -65,7 +65,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
initWebView();
loginBinding.desktopMode.setOnCheckedChangeListener(this);
loginBinding.cookies.setOnClickListener(this);
loginBinding.refresh.setOnClickListener(this);
}
@ -86,23 +85,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
}
}
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
final WebSettings webSettings = loginBinding.webView.getSettings();
final String newUserAgent = isChecked
? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
: defaultUserAgent;
webSettings.setUserAgentString(newUserAgent);
webSettings.setUseWideViewPort(isChecked);
webSettings.setLoadWithOverviewMode(isChecked);
webSettings.setSupportZoom(isChecked);
webSettings.setBuiltInZoomControls(isChecked);
loginBinding.webView.loadUrl("https://instagram.com/");
}
@SuppressLint("SetJavaScriptEnabled")
private void initWebView() {
if (loginBinding != null) {
@ -110,7 +92,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
loginBinding.webView.setWebViewClient(webViewClient);
final WebSettings webSettings = loginBinding.webView.getSettings();
if (webSettings != null) {
if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString();
webSettings.setUserAgentString("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36");
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setSupportZoom(true);

View File

@ -8,19 +8,18 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.provider.BaseColumns;
import android.text.Editable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.IdRes;
@ -28,14 +27,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.provider.FontRequest;
import androidx.emoji.text.EmojiCompat;
import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
@ -50,30 +47,27 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.textfield.TextInputLayout;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
import awais.instagrabber.fragments.main.FeedFragment;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.Tab;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.repositories.responses.search.SearchItem;
import awais.instagrabber.repositories.responses.search.SearchResponse;
import awais.instagrabber.services.ActivityCheckerService;
import awais.instagrabber.services.DMSyncAlarmReceiver;
import awais.instagrabber.utils.AppExecutors;
@ -86,10 +80,7 @@ import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.emoji.EmojiParser;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.webservices.SearchService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import awais.instagrabber.webservices.RetrofitFactory;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -98,16 +89,21 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private static final String TAG = "MainActivity";
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
private static final List<Integer> SEARCH_VISIBLE_DESTINATIONS = ImmutableList.of(
R.id.feedFragment,
R.id.profileFragment,
R.id.directMessagesInboxFragment,
R.id.discoverFragment,
R.id.favoritesFragment,
R.id.hashTagFragment,
R.id.locationFragment
);
private static MainActivity instance;
private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData;
private MenuItem searchMenuItem;
private SuggestionsAdapter suggestionAdapter;
private AutoCompleteTextView searchAutoComplete;
private SearchView searchView;
private SearchService searchService;
private boolean showSearch = true;
private Handler suggestionsFetchHandler;
private int firstFragmentGraphIndex;
private int lastSelectedNavMenuId;
private boolean isActivityCheckerServiceBound = false;
@ -131,13 +127,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
};
public static MainActivity getInstance() {
return instance;
}
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = this;
binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE);
CookieUtils.setupCookies(cookie);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
setupCookie();
if (settingsHelper.getBoolean(Constants.FLAG_SECURE))
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
setContentView(binding.getRoot());
@ -154,7 +153,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
if (savedInstanceState == null) {
setupBottomNavigationBar(true);
}
setupSuggestions();
if (!BuildConfig.isPre) {
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this);
@ -163,7 +161,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
final Intent intent = getIntent();
handleIntent(intent);
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
if (isLoggedIn && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
bindActivityCheckerService();
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
@ -173,9 +171,29 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
EmojiVariantManager.getInstance();
});
initEmojiCompat();
searchService = SearchService.getInstance();
// initDmService();
initDmUnreadCount();
initSearchInput();
}
private void setupCookie() {
final String cookie = settingsHelper.getString(Constants.COOKIE);
long userId = 0;
String csrfToken = null;
if (!TextUtils.isEmpty(cookie)) {
userId = CookieUtils.getUserIdFromCookie(cookie);
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
}
if (TextUtils.isEmpty(cookie) || userId == 0 || TextUtils.isEmpty(csrfToken)) {
isLoggedIn = false;
return;
}
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
if (TextUtils.isEmpty(deviceUuid)) {
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
}
CookieUtils.setupCookies(cookie);
isLoggedIn = true;
}
private void initDmService() {
@ -195,25 +213,74 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
});
}
private void initSearchInput() {
binding.searchInputLayout.setEndIconOnClickListener(v -> {
final EditText editText = binding.searchInputLayout.getEditText();
if (editText == null) return;
editText.setText("");
});
binding.searchInputLayout.addOnEditTextAttachedListener(textInputLayout -> {
textInputLayout.setEndIconVisible(false);
final EditText editText = textInputLayout.getEditText();
if (editText == null) return;
editText.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void afterTextChanged(final Editable s) {
binding.searchInputLayout.setEndIconVisible(!TextUtils.isEmpty(s));
}
});
});
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
searchMenuItem = menu.findItem(R.id.search);
if (showSearch && currentNavControllerLiveData != null) {
final NavController navController = currentNavControllerLiveData.getValue();
if (navController != null) {
final NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination != null) {
final int destinationId = currentDestination.getId();
showSearch = destinationId == R.id.profileFragment;
}
final NavController navController = currentNavControllerLiveData.getValue();
if (navController != null) {
final NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination != null) {
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), currentDestination.getId());
}
}
if (!showSearch) {
searchMenuItem.setVisible(false);
return true;
// if (binding.searchInputLayout.getVisibility() == View.VISIBLE) {
// searchMenuItem.setVisible(false).setEnabled(false);
// return true;
// }
// searchMenuItem.setVisible(true).setEnabled(true);
// if (showSearch && currentNavControllerLiveData != null) {
// final NavController navController = currentNavControllerLiveData.getValue();
// if (navController != null) {
// final NavDestination currentDestination = navController.getCurrentDestination();
// if (currentDestination != null) {
// final int destinationId = currentDestination.getId();
// showSearch = destinationId == R.id.profileFragment;
// }
// }
// }
// if (!showSearch) {
// searchMenuItem.setVisible(false);
// return true;
// }
// return setupSearchView();
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.search) {
final NavController navController = currentNavControllerLiveData.getValue();
if (navController == null) return false;
try {
navController.navigate(R.id.action_global_search);
return true;
} catch (Exception e) {
Log.e(TAG, "onOptionsItemSelected: ", e);
}
return false;
}
return setupSearchView();
return super.onOptionsItemSelected(item);
}
@Override
@ -263,6 +330,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
Log.e(TAG, "onDestroy: ", e);
}
unbindActivityCheckerService();
try {
RetrofitFactory.getInstance().destroy();
} catch (Exception e) {
Log.e(TAG, "onDestroy: ", e);
}
instance = null;
}
@Override
@ -314,176 +387,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
notificationManager.createNotificationChannel(silentNotificationChannel);
}
private void setupSuggestions() {
suggestionsFetchHandler = new Handler();
suggestionAdapter = new SuggestionsAdapter(this, (type, query) -> {
if (searchMenuItem != null) searchMenuItem.collapseActionView();
if (searchView != null && !searchView.isIconified()) searchView.setIconified(true);
if (currentNavControllerLiveData == null) return;
final NavController navController = currentNavControllerLiveData.getValue();
if (navController == null) return;
final Bundle bundle = new Bundle();
switch (type) {
case TYPE_LOCATION:
bundle.putLong("locationId", Long.parseLong(query));
navController.navigate(R.id.action_global_locationFragment, bundle);
break;
case TYPE_HASHTAG:
bundle.putString("hashtag", query);
navController.navigate(R.id.action_global_hashTagFragment, bundle);
break;
case TYPE_USER:
bundle.putString("username", query);
navController.navigate(R.id.action_global_profileFragment, bundle);
break;
}
});
}
private boolean setupSearchView() {
final View actionView = searchMenuItem.getActionView();
if (!(actionView instanceof SearchView)) return false;
searchView = (SearchView) actionView;
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setMaxWidth(Integer.MAX_VALUE);
final View searchText = searchView.findViewById(R.id.search_src_text);
if (searchText instanceof AutoCompleteTextView) {
searchAutoComplete = (AutoCompleteTextView) searchText;
}
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
private boolean searchUser;
private boolean searchHash;
private Call<SearchResponse> prevSuggestionAsync;
private final String[] COLUMNS = {
BaseColumns._ID,
Constants.EXTRAS_USERNAME,
Constants.EXTRAS_NAME,
Constants.EXTRAS_TYPE,
"query",
"pfp",
"verified"
};
private String currentSearchQuery;
private final Callback<SearchResponse> cb = new Callback<SearchResponse>() {
@Override
public void onResponse(@NonNull final Call<SearchResponse> call,
@NonNull final Response<SearchResponse> response) {
final MatrixCursor cursor;
final SearchResponse body = response.body();
if (body == null) {
cursor = null;
return;
}
final List<SearchItem> result = new ArrayList<>();
if (isLoggedIn) {
if (body.getList() != null) {
result.addAll(searchHash ? body.getList()
.stream()
.filter(i -> i.getUser() == null)
.collect(Collectors.toList())
: body.getList());
}
} else {
if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers());
if (body.getHashtags() != null) result.addAll(body.getHashtags());
if (body.getPlaces() != null) result.addAll(body.getPlaces());
}
cursor = new MatrixCursor(COLUMNS, 0);
for (int i = 0; i < result.size(); i++) {
final SearchItem suggestionModel = result.get(i);
if (suggestionModel != null) {
Object[] objects = null;
if (suggestionModel.getUser() != null)
objects = new Object[]{
suggestionModel.getPosition(),
suggestionModel.getUser().getUsername(),
suggestionModel.getUser().getFullName(),
SuggestionType.TYPE_USER,
suggestionModel.getUser().getUsername(),
suggestionModel.getUser().getProfilePicUrl(),
suggestionModel.getUser().isVerified()};
else if (suggestionModel.getHashtag() != null)
objects = new Object[]{
suggestionModel.getPosition(),
suggestionModel.getHashtag().getName(),
suggestionModel.getHashtag().getSubtitle(),
SuggestionType.TYPE_HASHTAG,
suggestionModel.getHashtag().getName(),
"res:/" + R.drawable.ic_hashtag,
false};
else if (suggestionModel.getPlace() != null)
objects = new Object[]{
suggestionModel.getPosition(),
suggestionModel.getPlace().getTitle(),
suggestionModel.getPlace().getSubtitle(),
SuggestionType.TYPE_LOCATION,
suggestionModel.getPlace().getLocation().getPk(),
"res:/" + R.drawable.ic_location,
false};
cursor.addRow(objects);
}
}
suggestionAdapter.changeCursor(cursor);
}
@Override
public void onFailure(@NonNull final Call<SearchResponse> call,
@NonNull Throwable t) {
if (!call.isCanceled()) {
Log.e(TAG, "Exception on search:", t);
}
}
};
private final Runnable runnable = () -> {
cancelSuggestionsAsync();
if (TextUtils.isEmpty(currentSearchQuery)) {
suggestionAdapter.changeCursor(null);
return;
}
searchUser = currentSearchQuery.charAt(0) == '@';
searchHash = currentSearchQuery.charAt(0) == '#';
if (currentSearchQuery.length() == 1 && (searchHash || searchUser)) {
if (searchAutoComplete != null) {
searchAutoComplete.setThreshold(2);
}
} else {
if (searchAutoComplete != null) {
searchAutoComplete.setThreshold(1);
}
prevSuggestionAsync = searchService.search(isLoggedIn,
searchUser || searchHash ? currentSearchQuery.substring(1)
: currentSearchQuery,
searchUser ? "user" : (searchHash ? "hashtag" : "blended"));
suggestionAdapter.changeCursor(null);
prevSuggestionAsync.enqueue(cb);
}
};
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try {
prevSuggestionAsync.cancel();
} catch (final Exception ignored) {}
}
@Override
public boolean onQueryTextSubmit(final String query) {
return onQueryTextChange(query);
}
@Override
public boolean onQueryTextChange(final String query) {
suggestionsFetchHandler.removeCallbacks(runnable);
currentSearchQuery = query;
suggestionsFetchHandler.postDelayed(runnable, 800);
return true;
}
});
return true;
}
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
final List<Integer> mainNavList = currentTabs.stream()
@ -506,16 +409,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
firstFragmentGraphIndex);
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
currentNavControllerLiveData = navControllerLiveData;
binding.bottomNavView.setOnNavigationItemReselectedListener(item -> {
// Log.d(TAG, "setupBottomNavigationBar: item: " + item);
final Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
if (navHostFragment != null) {
final Fragment fragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
if (fragment instanceof FeedFragment) {
((FeedFragment) fragment).scrollToTop();
}
}
});
}
private void setSelectedTab(final List<Tab> tabs) {
@ -542,6 +435,13 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private List<Tab> setupAnonBottomNav() {
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
final Tab favoriteTab = new Tab(R.drawable.ic_star_24,
getString(R.string.title_favorites),
false,
"favorites_nav_graph",
R.navigation.favorites_nav_graph,
R.id.favorites_nav_graph,
R.id.favoritesFragment);
final Tab profileTab = new Tab(R.drawable.ic_person_24,
getString(R.string.profile),
false,
@ -558,12 +458,15 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.morePreferencesFragment);
final Menu menu = binding.bottomNavView.getMenu();
menu.clear();
menu.add(0, favoriteTab.getNavigationRootId(), 0, favoriteTab.getTitle()).setIcon(favoriteTab.getIconResId());
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) {
if (selectedItemId != R.id.profile_nav_graph
&& selectedItemId != R.id.more_nav_graph
&& selectedItemId != R.id.favorites_nav_graph) {
setBottomNavSelectedTab(profileTab);
}
return ImmutableList.of(profileTab, moreTab);
return ImmutableList.of(favoriteTab, profileTab, moreTab);
}
private List<Tab> setupMainBottomNav() {
@ -584,20 +487,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
binding.bottomNavView.setSelectedItemId(navGraphRootId);
}
// @NonNull
// private List<Integer> getMainNavList(final int main_nav_ids) {
// final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
// final List<Integer> mainNavList = new ArrayList<>(navIds.length());
// final int length = navIds.length();
// for (int i = 0; i < length; i++) {
// final int resourceId = navIds.getResourceId(i, -1);
// if (resourceId < 0) continue;
// mainNavList.add(resourceId);
// }
// navIds.recycle();
// return mainNavList;
// }
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
if (navController == null) return;
NavigationUI.setupWithNavController(toolbar, navController);
@ -616,11 +505,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
final boolean contains = showBottomViewDestinations.contains(destinationId);
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView);
}
binding.getRoot().post(() -> {
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView);
}
});
// explicitly hide keyboard when we navigate
final View view = getCurrentFocus();
Utils.hideKeyboard(view);
@ -629,12 +519,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private void setupMenu(final int backStackSize, final int destinationId) {
if (searchMenuItem == null) return;
if (backStackSize >= 2 && destinationId == R.id.profileFragment) {
showSearch = true;
if (backStackSize >= 2 && SEARCH_VISIBLE_DESTINATIONS.contains(destinationId)) {
searchMenuItem.setVisible(true);
return;
}
showSearch = false;
searchMenuItem.setVisible(false);
}
@ -765,7 +653,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
if (navController == null) return;
final Bundle bundle = new Bundle();
bundle.putString("username", "@" + username);
navController.navigate(R.id.action_global_profileFragment, bundle);
try {
navController.navigate(R.id.action_global_profileFragment, bundle);
} catch (Exception e) {
Log.e(TAG, "showProfileView: ", e);
}
}
private void showPostView(@NonNull final IntentModel intentModel) {
@ -778,11 +670,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
alertDialog.show();
new PostFetcher(shortCode, feedModel -> {
if (feedModel != null) {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog -> alertDialog.dismiss());
fragment.show(getSupportFragmentManager(), "post_view");
if (currentNavControllerLiveData == null) return;
final NavController navController = currentNavControllerLiveData.getValue();
if (navController == null) return;
final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "showPostView: ", e);
}
return;
}
Toast.makeText(getApplicationContext(), R.string.post_not_found, Toast.LENGTH_SHORT).show();
@ -838,11 +735,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
public void setCollapsingView(@NonNull final View view) {
binding.collapsingToolbarLayout.addView(view, 0);
try {
binding.collapsingToolbarLayout.addView(view, 0);
} catch (Exception e) {
Log.e(TAG, "setCollapsingView: ", e);
}
}
public void removeCollapsingView(@NonNull final View view) {
binding.collapsingToolbarLayout.removeView(view);
try {
binding.collapsingToolbarLayout.removeView(view);
} catch (Exception e) {
Log.e(TAG, "removeCollapsingView: ", e);
}
}
public void setToolbar(final Toolbar toolbar) {
@ -905,14 +810,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return binding.toolbar;
}
public View getRootView() {
return binding.getRoot();
}
public List<Tab> getCurrentTabs() {
return currentTabs;
}
// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) {
// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId);
// }
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
if (badge == null) return;
@ -927,4 +832,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
badge.setNumber(unseenCount);
badge.setVisible(true);
}
@NonNull
public TextInputLayout showSearchView() {
binding.searchInputLayout.setVisibility(View.VISIBLE);
return binding.searchInputLayout;
}
public void hideSearchView() {
binding.searchInputLayout.setVisibility(View.GONE);
}
}

View File

@ -1,195 +1,60 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.adapters.viewholder.comments.ChildCommentViewHolder;
import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.databinding.ItemCommentSmallBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.Comment;
public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerView.ViewHolder> {
private static final int TYPE_PARENT = 1;
private static final int TYPE_CHILD = 2;
private final Map<Integer, Integer> positionTypeMap = new HashMap<>();
// private final Filter filter = new Filter() {
// @NonNull
// @Override
// protected FilterResults performFiltering(final CharSequence filter) {
// final FilterResults results = new FilterResults();
// results.values = commentModels;
//
// final int commentsLen = commentModels == null ? 0 : commentModels.size();
// if (commentModels != null && commentsLen > 0 && !TextUtils.isEmpty(filter)) {
// final String query = filter.toString().toLowerCase();
// final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen);
//
// for (final CommentModel commentModel : commentModels) {
// final String commentText = commentModel.getText().toString().toLowerCase();
//
// if (commentText.contains(query)) filterList.add(commentModel);
// else {
// final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
// if (childCommentModels != null) {
// for (final CommentModel childCommentModel : childCommentModels) {
// final String childCommentText = childCommentModel.getText().toString().toLowerCase();
// if (childCommentText.contains(query)) filterList.add(commentModel);
// }
// }
// }
// }
// filterList.trimToSize();
// results.values = filterList.toArray(new CommentModel[0]);
// }
//
// return results;
// }
//
// @Override
// protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) {
// if (results.values instanceof List) {
// //noinspection unchecked
// filteredCommentModels = (List<CommentModel>) results.values;
// notifyDataSetChanged();
// }
// }
// };
private static final DiffUtil.ItemCallback<CommentModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentModel>() {
public final class CommentsAdapter extends ListAdapter<Comment, CommentViewHolder> {
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() {
@Override
public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
return oldItem.getId().equals(newItem.getId());
public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
return Objects.equals(oldItem.getId(), newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
return oldItem.getId().equals(newItem.getId());
public boolean areContentsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
return Objects.equals(oldItem, newItem);
}
};
private final CommentCallback commentCallback;
private CommentModel selected, toChangeLike;
private int selectedIndex, likedIndex;
public CommentsAdapter(final CommentCallback commentCallback) {
private final boolean showingReplies;
private final CommentCallback commentCallback;
private final long currentUserId;
public CommentsAdapter(final long currentUserId,
final boolean showingReplies,
final CommentCallback commentCallback) {
super(DIFF_CALLBACK);
this.showingReplies = showingReplies;
this.currentUserId = currentUserId;
this.commentCallback = commentCallback;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final Context context = parent.getContext();
final LayoutInflater layoutInflater = LayoutInflater.from(context);
if (type == TYPE_PARENT) {
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
return new ParentCommentViewHolder(binding);
}
final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
return new ChildCommentViewHolder(binding);
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
return new CommentViewHolder(binding, currentUserId, commentCallback);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
CommentModel commentModel = getItem(position);
if (commentModel == null) return;
final int type = getItemViewType(position);
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId());
if (toLike) commentModel = this.toChangeLike;
if (type == TYPE_PARENT) {
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
viewHolder.bind(commentModel, selected, commentCallback);
return;
}
final ChildCommentViewHolder viewHolder = (ChildCommentViewHolder) holder;
viewHolder.bind(commentModel, selected, commentCallback);
}
@Override
public void submitList(@Nullable final List<CommentModel> list) {
final List<CommentModel> flatList = flattenList(list);
super.submitList(flatList);
}
@Override
public void submitList(@Nullable final List<CommentModel> list, @Nullable final Runnable commitCallback) {
final List<CommentModel> flatList = flattenList(list);
super.submitList(flatList, commitCallback);
}
private List<CommentModel> flattenList(final List<CommentModel> list) {
if (list == null) {
return Collections.emptyList();
}
final List<CommentModel> flatList = new ArrayList<>();
int lastCommentIndex = -1;
for (final CommentModel parent : list) {
lastCommentIndex++;
flatList.add(parent);
positionTypeMap.put(lastCommentIndex, TYPE_PARENT);
final List<CommentModel> children = parent.getChildCommentModels();
if (children != null) {
for (final CommentModel child : children) {
lastCommentIndex++;
flatList.add(child);
positionTypeMap.put(lastCommentIndex, TYPE_CHILD);
}
}
}
return flatList;
}
@Override
public int getItemViewType(final int position) {
final Integer type = positionTypeMap.get(position);
if (type == null) {
return TYPE_PARENT;
}
return type;
}
public void setSelected(final CommentModel commentModel) {
this.selected = commentModel;
selectedIndex = getCurrentList().indexOf(commentModel);
notifyItemChanged(selectedIndex);
}
public void clearSelection() {
this.selected = null;
notifyItemChanged(selectedIndex);
}
public void setLiked(final CommentModel commentModel, final boolean liked) {
likedIndex = getCurrentList().indexOf(commentModel);
CommentModel newCommentModel = commentModel;
newCommentModel.setLiked(liked);
this.toChangeLike = newCommentModel;
notifyItemChanged(likedIndex);
}
public CommentModel getSelected() {
return selected;
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
final Comment comment = getItem(position);
holder.bind(comment, showingReplies && position == 0, showingReplies && position != 0);
}
public interface CommentCallback {
void onClick(final CommentModel comment);
void onClick(final Comment comment);
void onHashtagClick(final String hashtag);
@ -198,5 +63,15 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
void onURLClick(final String url);
void onEmailClick(final String emailAddress);
void onLikeClick(final Comment comment, boolean liked, final boolean isReply);
void onRepliesClick(final Comment comment);
void onViewLikes(Comment comment);
void onTranslate(Comment comment);
void onDelete(Comment comment, boolean isReply);
}
}

View File

@ -406,6 +406,8 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
void onReactionClick(DirectItem item, int position);
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
void onAddReactionListener(DirectItem item);
}
public interface DirectItemInternalLongClickListener {

View File

@ -19,7 +19,7 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.databinding.ItemSearchResultBinding;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
@ -73,7 +73,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
// header
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
}
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false);
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(inflater, parent, false);
return new FavoriteViewHolder(binding);
}

View File

@ -34,7 +34,7 @@ public final class LikesAdapter extends RecyclerView.Adapter<FollowsViewHolder>
@Override
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
final User model = profileModels.get(position);
holder.bind(model, null, onClickListener);
holder.bind(model, onClickListener);
}
@Override

View File

@ -0,0 +1,33 @@
package awais.instagrabber.adapters;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.List;
import awais.instagrabber.fragments.search.SearchCategoryFragment;
import awais.instagrabber.models.enums.FavoriteType;
public class SearchCategoryAdapter extends FragmentStateAdapter {
private final List<FavoriteType> categories;
public SearchCategoryAdapter(@NonNull final Fragment fragment,
@NonNull final List<FavoriteType> categories) {
super(fragment);
this.categories = categories;
}
@NonNull
@Override
public Fragment createFragment(final int position) {
return SearchCategoryFragment.newInstance(categories.get(position));
}
@Override
public int getItemCount() {
return categories.size();
}
}

View File

@ -0,0 +1,215 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.AdapterListUpdateCallback;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.AsyncListDiffer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.SearchItemViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.ItemSearchResultBinding;
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.repositories.responses.search.SearchItem;
public final class SearchItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = SearchItemsAdapter.class.getSimpleName();
private static final DiffUtil.ItemCallback<SearchItemOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<SearchItemOrHeader>() {
@Override
public boolean areItemsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
return Objects.equals(oldItem, newItem);
}
@Override
public boolean areContentsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
return Objects.equals(oldItem, newItem);
}
};
private static final String RECENT = "recent";
private static final String FAVORITE = "favorite";
private static final int VIEW_TYPE_HEADER = 0;
private static final int VIEW_TYPE_ITEM = 1;
private final OnSearchItemClickListener onSearchItemClickListener;
private final AsyncListDiffer<SearchItemOrHeader> differ;
public SearchItemsAdapter(final OnSearchItemClickListener onSearchItemClickListener) {
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(DIFF_CALLBACK).build());
this.onSearchItemClickListener = onSearchItemClickListener;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if (viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false));
}
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(layoutInflater, parent, false);
return new SearchItemViewHolder(binding, onSearchItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
final SearchItemOrHeader searchItemOrHeader = getItem(position);
if (!searchItemOrHeader.isHeader()) return;
((HeaderViewHolder) holder).bind(searchItemOrHeader.header);
return;
}
((SearchItemViewHolder) holder).bind(getItem(position).searchItem);
}
protected SearchItemOrHeader getItem(int position) {
return differ.getCurrentList().get(position);
}
@Override
public int getItemCount() {
return differ.getCurrentList().size();
}
@Override
public int getItemViewType(final int position) {
return getItem(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
}
public void submitList(@Nullable final List<SearchItem> list) {
if (list == null) {
differ.submitList(null);
return;
}
differ.submitList(sectionAndSort(list));
}
public void submitList(@Nullable final List<SearchItem> list, @Nullable final Runnable commitCallback) {
if (list == null) {
differ.submitList(null, commitCallback);
return;
}
differ.submitList(sectionAndSort(list), commitCallback);
}
@NonNull
private List<SearchItemOrHeader> sectionAndSort(@NonNull final List<SearchItem> list) {
final boolean containsRecentOrFavorite = list.stream().anyMatch(searchItem -> searchItem.isRecent() || searchItem.isFavorite());
// Don't do anything if not showing recent results
if (!containsRecentOrFavorite) {
return list.stream()
.map(SearchItemOrHeader::new)
.collect(Collectors.toList());
}
final List<SearchItem> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
final boolean bothRecent = o1.isRecent() && o2.isRecent();
if (bothRecent) {
// Don't sort
return 0;
}
final boolean bothFavorite = o1.isFavorite() && o2.isFavorite();
if (bothFavorite) {
if (o1.getType() == o2.getType()) return 0;
// keep users at top
if (o1.getType() == FavoriteType.USER) return -1;
if (o2.getType() == FavoriteType.USER) return 1;
// keep locations at bottom
if (o1.getType() == FavoriteType.LOCATION) return 1;
if (o2.getType() == FavoriteType.LOCATION) return -1;
}
// keep recents at top
if (o1.isRecent()) return -1;
if (o2.isRecent()) return 1;
return 0;
});
final List<SearchItemOrHeader> itemOrHeaders = new ArrayList<>();
for (int i = 0; i < listCopy.size(); i++) {
final SearchItem searchItem = listCopy.get(i);
final SearchItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
boolean prevWasSameType = prev != null && ((prev.searchItem.isRecent() && searchItem.isRecent())
|| (prev.searchItem.isFavorite() && searchItem.isFavorite()));
if (prevWasSameType) {
// just add the item
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
continue;
}
// add header and item
// add header only if search item is recent or favorite
if (searchItem.isRecent() || searchItem.isFavorite()) {
itemOrHeaders.add(new SearchItemOrHeader(searchItem.isRecent() ? RECENT : FAVORITE));
}
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
}
return itemOrHeaders;
}
private static class SearchItemOrHeader {
String header;
SearchItem searchItem;
public SearchItemOrHeader(final SearchItem searchItem) {
this.searchItem = searchItem;
}
public SearchItemOrHeader(final String header) {
this.header = header;
}
boolean isHeader() {
return header != null;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final SearchItemOrHeader that = (SearchItemOrHeader) o;
return Objects.equals(header, that.header) &&
Objects.equals(searchItem, that.searchItem);
}
@Override
public int hashCode() {
return Objects.hash(header, searchItem);
}
}
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
private final ItemFavSectionHeaderBinding binding;
public HeaderViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final String header) {
if (header == null) return;
final int headerText;
switch (header) {
case RECENT:
headerText = R.string.recent;
break;
case FAVORITE:
headerText = R.string.title_favorites;
break;
default:
headerText = R.string.unknown;
break;
}
binding.getRoot().setText(headerText);
}
}
}

View File

@ -1,11 +1,17 @@
package awais.instagrabber.adapters;
import android.view.View;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import awais.instagrabber.repositories.responses.Media;
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
@Override
public void onThumbnailLoaded(final int position) {}
@Override
public void onItemClicked(final int position) {}
public void onItemClicked(final int position, final Media media, final View view) {}
@Override
public void onPlayerPlay(final int position) {}
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
@Override
public void onPlayerRelease(final int position) {}
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
@Override
public boolean isInFullScreen() {
return false;
}
}

View File

@ -1,28 +1,27 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.Media;
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
private final boolean loadVideoOnItemClick;
private final SliderCallback sliderCallback;
private final LayoutExoCustomControlsBinding controlsBinding;
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
@Override
@ -36,15 +35,11 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
}
};
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick,
public SliderItemsAdapter(final boolean loadVideoOnItemClick,
final SliderCallback sliderCallback) {
super(DIFF_CALLBACK);
this.onVerticalDragListener = onVerticalDragListener;
this.loadVideoOnItemClick = loadVideoOnItemClick;
this.sliderCallback = sliderCallback;
this.controlsBinding = controlsBinding;
}
@NonNull
@ -55,12 +50,12 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
switch (mediaItemType) {
case MEDIA_TYPE_VIDEO: {
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding, loadVideoOnItemClick);
return new SliderVideoViewHolder(binding, loadVideoOnItemClick);
}
case MEDIA_TYPE_IMAGE:
default:
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
return new SliderPhotoViewHolder(binding, onVerticalDragListener);
return new SliderPhotoViewHolder(binding);
}
}
@ -143,12 +138,16 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
public interface SliderCallback {
void onThumbnailLoaded(int position);
void onItemClicked(int position);
void onItemClicked(int position, final Media media, final View view);
void onPlayerPlay(int position);
void onPlayerPause(int position);
void onPlayerRelease(int position);
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
boolean isInFullScreen();
}
}

View File

@ -1,77 +0,0 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.CursorAdapter;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.models.enums.SuggestionType;
public final class SuggestionsAdapter extends CursorAdapter {
private static final String TAG = "SuggestionsAdapter";
private final OnSuggestionClickListener onSuggestionClickListener;
public SuggestionsAdapter(final Context context,
final OnSuggestionClickListener onSuggestionClickListener) {
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER);
this.onSuggestionClickListener = onSuggestionClickListener;
}
@Override
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
final LayoutInflater layoutInflater = LayoutInflater.from(context);
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(layoutInflater, parent, false);
return binding.getRoot();
// return layoutInflater.inflate(R.layout.item_suggestion, parent, false);
}
@Override
public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) {
// i, username, fullname, type, query, picUrl, verified
// 0, 1 , 2 , 3 , 4 , 5 , 6
final String fullName = cursor.getString(2);
String username = cursor.getString(1);
String picUrl = cursor.getString(5);
final boolean verified = cursor.getString(6).charAt(0) == 't';
final String type = cursor.getString(3);
SuggestionType suggestionType = null;
try {
suggestionType = SuggestionType.valueOf(type);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unknown suggestion type: " + type, e);
}
if (suggestionType == null) return;
String query = cursor.getString(4);
switch (suggestionType) {
case TYPE_USER:
username = '@' + username;
break;
case TYPE_HASHTAG:
username = '#' + username;
break;
}
if (onSuggestionClickListener != null) {
final SuggestionType finalSuggestionType = suggestionType;
view.setOnClickListener(v -> onSuggestionClickListener.onSuggestionClick(finalSuggestionType, query));
}
final ItemSuggestionBinding binding = ItemSuggestionBinding.bind(view);
binding.isVerified.setVisibility(verified ? View.VISIBLE : View.GONE);
binding.tvUsername.setText(username);
binding.tvFullName.setVisibility(View.VISIBLE);
binding.tvFullName.setText(fullName);
binding.ivProfilePic.setImageURI(picUrl);
}
public interface OnSuggestionClickListener {
void onSuggestionClick(final SuggestionType type, final String query);
}
}

View File

@ -0,0 +1,209 @@
package awais.instagrabber.adapters.viewholder;
import android.content.Context;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.Menu;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
import awais.instagrabber.customviews.ProfilePicView;
import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.models.Comment;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Utils;
public final class CommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentBinding binding;
private final long currentUserId;
private final CommentCallback commentCallback;
@ColorInt
private int parentCommentHighlightColor;
private PopupMenu optionsPopup;
public CommentViewHolder(@NonNull final ItemCommentBinding binding,
final long currentUserId,
final CommentCallback commentCallback) {
super(binding.getRoot());
this.binding = binding;
this.currentUserId = currentUserId;
this.commentCallback = commentCallback;
final Context context = itemView.getContext();
if (context == null) return;
final Resources.Theme theme = context.getTheme();
if (theme == null) return;
final TypedValue typedValue = new TypedValue();
final boolean resolved = theme.resolveAttribute(R.attr.parentCommentHighlightColor, typedValue, true);
if (resolved) {
parentCommentHighlightColor = typedValue.data;
}
}
public void bind(final Comment comment, final boolean isReplyParent, final boolean isReply) {
if (comment == null) return;
itemView.setOnClickListener(v -> {
if (commentCallback != null) {
commentCallback.onClick(comment);
}
});
if (isReplyParent && parentCommentHighlightColor != 0) {
itemView.setBackgroundColor(parentCommentHighlightColor);
} else {
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
}
setupCommentText(comment, isReply);
binding.date.setText(comment.getDateTime());
setLikes(comment, isReply);
setReplies(comment, isReply);
setUser(comment, isReply);
setupOptions(comment, isReply);
}
private void setupCommentText(@NonNull final Comment comment, final boolean isReply) {
binding.comment.clearOnURLClickListeners();
binding.comment.clearOnHashtagClickListeners();
binding.comment.clearOnMentionClickListeners();
binding.comment.clearOnEmailClickListeners();
binding.comment.setText(comment.getText());
binding.comment.setTextSize(TypedValue.COMPLEX_UNIT_SP, isReply ? 12 : 14);
binding.comment.addOnHashtagListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onHashtagClick(originalText);
});
binding.comment.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onMentionClick(originalText);
});
binding.comment.addOnEmailClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onEmailClick(originalText);
});
binding.comment.addOnURLClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onURLClick(originalText);
});
binding.comment.setOnLongClickListener(v -> {
Utils.copyText(itemView.getContext(), comment.getText());
return true;
});
binding.comment.setOnClickListener(v -> commentCallback.onClick(comment));
}
private void setUser(@NonNull final Comment comment, final boolean isReply) {
final User user = comment.getUser();
if (user == null) return;
binding.username.setUsername(user.getUsername(), user.isVerified());
binding.username.setTextAppearance(itemView.getContext(), isReply ? R.style.TextAppearance_MaterialComponents_Subtitle2
: R.style.TextAppearance_MaterialComponents_Subtitle1);
binding.username.setOnClickListener(v -> {
if (commentCallback == null) return;
commentCallback.onMentionClick("@" + user.getUsername());
});
binding.profilePic.setImageURI(user.getProfilePicUrl());
binding.profilePic.setSize(isReply ? ProfilePicView.Size.SMALLER : ProfilePicView.Size.SMALL);
binding.profilePic.setOnClickListener(v -> {
if (commentCallback == null) return;
commentCallback.onMentionClick("@" + user.getUsername());
});
}
private void setLikes(@NonNull final Comment comment, final boolean isReply) {
// final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.likes.setText(String.valueOf(comment.getLikes()));
binding.likes.setOnLongClickListener(v -> {
if (commentCallback == null) return false;
commentCallback.onViewLikes(comment);
return true;
});
if (currentUserId == 0) { // not logged in
binding.likes.setOnClickListener(v -> {
if (commentCallback == null) return;
commentCallback.onViewLikes(comment);
});
return;
}
final boolean liked = comment.getLiked();
final int resId = liked ? R.drawable.ic_like : R.drawable.ic_not_liked;
binding.likes.setCompoundDrawablesRelativeWithSize(ContextCompat.getDrawable(itemView.getContext(), resId), null, null, null);
binding.likes.setOnClickListener(v -> {
if (commentCallback == null) return;
// toggle like
commentCallback.onLikeClick(comment, !liked, isReply);
});
}
private void setReplies(@NonNull final Comment comment, final boolean isReply) {
final int replies = comment.getReplyCount();
binding.replies.setVisibility(View.VISIBLE);
final String text = isReply ? "" : String.valueOf(replies);
// final String string = itemView.getResources().getQuantityString(R.plurals.replies_count, replies, replies);
binding.replies.setText(text);
binding.replies.setOnClickListener(v -> {
if (commentCallback == null) return;
commentCallback.onRepliesClick(comment);
});
}
private void setupOptions(final Comment comment, final boolean isReply) {
binding.options.setOnClickListener(v -> {
if (optionsPopup == null) {
createOptionsPopupMenu(comment, isReply);
}
if (optionsPopup == null) return;
optionsPopup.show();
});
}
private void createOptionsPopupMenu(final Comment comment, final boolean isReply) {
if (optionsPopup == null) {
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(itemView.getContext(), R.style.popupMenuStyle);
optionsPopup = new PopupMenu(themeWrapper, binding.options);
} else {
optionsPopup.getMenu().clear();
}
optionsPopup.getMenuInflater().inflate(R.menu.comment_options_menu, optionsPopup.getMenu());
final User user = comment.getUser();
if (currentUserId == 0 || user == null || user.getPk() != currentUserId) {
final Menu menu = optionsPopup.getMenu();
menu.removeItem(R.id.delete);
}
optionsPopup.setOnMenuItemClickListener(item -> {
if (commentCallback == null) return false;
int itemId = item.getItemId();
if (itemId == R.id.translate) {
commentCallback.onTranslate(comment);
return true;
}
if (itemId == R.id.delete) {
commentCallback.onDelete(comment, isReply);
}
return true;
});
}
// private void setupReply(final Comment comment) {
// if (!isLoggedIn) {
// binding.reply.setVisibility(View.GONE);
// return;
// }
// binding.reply.setOnClickListener(v -> {
// if (commentCallback == null) return;
// // toggle like
// commentCallback.onReplyClick(comment);
// });
// }
}

View File

@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.FavoritesAdapter;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.databinding.ItemSearchResultBinding;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Constants;
@ -14,12 +14,12 @@ import awais.instagrabber.utils.Constants;
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "FavoriteViewHolder";
private final ItemSuggestionBinding binding;
private final ItemSearchResultBinding binding;
public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) {
public FavoriteViewHolder(@NonNull final ItemSearchResultBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.isVerified.setVisibility(View.GONE);
binding.verified.setVisibility(View.GONE);
}
public void bind(final Favorite model,
@ -36,12 +36,12 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
return longClickListener.onLongClick(model);
});
if (model.getType() == FavoriteType.HASHTAG) {
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
binding.profilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
} else {
binding.ivProfilePic.setImageURI(model.getPicUrl());
binding.profilePic.setImageURI(model.getPicUrl());
}
binding.tvFullName.setText(model.getDisplayName());
binding.tvUsername.setVisibility(View.VISIBLE);
binding.title.setVisibility(View.VISIBLE);
binding.subtitle.setText(model.getDisplayName());
String query = model.getQuery();
switch (model.getType()) {
case HASHTAG:
@ -51,11 +51,11 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
query = "@" + query;
break;
case LOCATION:
binding.tvUsername.setVisibility(View.GONE);
binding.title.setVisibility(View.GONE);
break;
default:
// do nothing
}
binding.tvUsername.setText(query);
binding.title.setText(query);
}
}

View File

@ -2,10 +2,9 @@ package awais.instagrabber.adapters.viewholder;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import awais.instagrabber.databinding.ItemFollowBinding;
import awais.instagrabber.models.FollowModel;
import awais.instagrabber.repositories.responses.User;
@ -14,23 +13,19 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
private final ItemFollowBinding binding;
public FollowsViewHolder(final ItemFollowBinding binding) {
public FollowsViewHolder(@NonNull final ItemFollowBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final User model,
final List<Long> admins,
final View.OnClickListener onClickListener) {
if (model == null) return;
itemView.setTag(model);
itemView.setOnClickListener(onClickListener);
binding.tvUsername.setText(model.getUsername());
binding.tvFullName.setText(model.getFullName());
if (admins != null && admins.contains(model.getPk())) {
binding.isAdmin.setVisibility(View.VISIBLE);
}
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
binding.username.setUsername("@" + model.getUsername(), model.isVerified());
binding.fullName.setText(model.getFullName());
binding.profilePic.setImageURI(model.getProfilePicUrl());
}
public void bind(final FollowModel model,
@ -38,8 +33,8 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
if (model == null) return;
itemView.setTag(model);
itemView.setOnClickListener(onClickListener);
binding.tvUsername.setText(model.getUsername());
binding.tvFullName.setText(model.getFullName());
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
binding.username.setUsername("@" + model.getUsername());
binding.fullName.setText(model.getFullName());
binding.profilePic.setImageURI(model.getProfilePicUrl());
}
}

View File

@ -0,0 +1,80 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemSearchResultBinding;
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Place;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.search.SearchItem;
public class SearchItemViewHolder extends RecyclerView.ViewHolder {
private final ItemSearchResultBinding binding;
private final OnSearchItemClickListener onSearchItemClickListener;
public SearchItemViewHolder(@NonNull final ItemSearchResultBinding binding,
final OnSearchItemClickListener onSearchItemClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onSearchItemClickListener = onSearchItemClickListener;
}
public void bind(final SearchItem searchItem) {
if (searchItem == null) return;
final FavoriteType type = searchItem.getType();
if (type == null) return;
String title;
String subtitle;
String picUrl;
boolean isVerified = false;
switch (type) {
case USER:
final User user = searchItem.getUser();
title = "@" + user.getUsername();
subtitle = user.getFullName();
picUrl = user.getProfilePicUrl();
isVerified = user.isVerified();
break;
case HASHTAG:
final Hashtag hashtag = searchItem.getHashtag();
title = "#" + hashtag.getName();
subtitle = hashtag.getSubtitle();
picUrl = "res:/" + R.drawable.ic_hashtag;
break;
case LOCATION:
final Place place = searchItem.getPlace();
title = place.getTitle();
subtitle = place.getSubtitle();
picUrl = "res:/" + R.drawable.ic_location;
break;
default:
return;
}
itemView.setOnClickListener(v -> {
if (onSearchItemClickListener != null) {
onSearchItemClickListener.onSearchItemClick(searchItem);
}
});
binding.delete.setVisibility(searchItem.isRecent() ? View.VISIBLE : View.GONE);
if (searchItem.isRecent()) {
binding.delete.setEnabled(true);
binding.delete.setOnClickListener(v -> {
if (onSearchItemClickListener != null) {
binding.delete.setEnabled(false);
onSearchItemClickListener.onSearchItemDelete(searchItem);
}
});
}
binding.title.setText(title);
binding.subtitle.setText(subtitle);
binding.profilePic.setImageURI(picUrl);
binding.verified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
}
}

View File

@ -2,7 +2,6 @@ package awais.instagrabber.adapters.viewholder;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.view.GestureDetector;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.ResponseBodyUtils;
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
private static final String TAG = "FeedSliderPhotoViewHolder";
private final ItemSliderPhotoBinding binding;
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
super(binding.getRoot());
this.binding = binding;
this.onVerticalDragListener = onVerticalDragListener;
}
public void bind(@NonNull final Media model,
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
})
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
.build());
// binding.getRoot().setOnClickListener(v -> {
// if (sliderCallback != null) {
// sliderCallback.onItemClicked(position);
// }
// });
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
@Override
public boolean onSingleTapUp(final MotionEvent e) {
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
return true;
sliderCallback.onItemClicked(position, model, binding.getRoot());
}
return false;
return super.onSingleTapConfirmed(e);
}
});
};
binding.getRoot().setTapListener(tapListener);
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
zoomableController.setMaxScaleFactor(3f);
binding.getRoot().setZoomableController(zoomableController);
if (onVerticalDragListener != null) {
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
}
binding.getRoot().setZoomingEnabled(true);
}
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
}

View File

@ -7,13 +7,13 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import java.util.List;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.VideoVersion;
@ -28,40 +28,23 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
private static final String TAG = "SliderVideoViewHolder";
private final LayoutVideoPlayerWithThumbnailBinding binding;
private final LayoutExoCustomControlsBinding controlsBinding;
private final boolean loadVideoOnItemClick;
private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
binding.playerView.performClick();
return true;
}
};
private VideoPlayerViewHelper videoPlayerViewHelper;
@SuppressLint("ClickableViewAccessibility")
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick) {
super(binding.getRoot());
this.binding = binding;
this.controlsBinding = controlsBinding;
this.loadVideoOnItemClick = loadVideoOnItemClick;
// if (onVerticalDragListener != null) {
// final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
// final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
// thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
// playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
// binding.thumbnailParent.setOnTouchListener((v, event) -> {
// final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
// if (onDragTouch) {
// return true;
// }
// return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
// });
// }
final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
binding.playerView.performClick();
return true;
}
};
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
binding.playerView.setOnTouchListener((v, event) -> {
gestureDetector.onTouchEvent(event);
@ -78,7 +61,7 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
@Override
public void onThumbnailClick() {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
sliderCallback.onItemClicked(position, media, binding.getRoot());
}
}
@ -121,6 +104,21 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
sliderCallback.onPlayerRelease(position);
}
}
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {
if (sliderCallback != null) {
sliderCallback.onFullScreenModeChanged(isFullScreen, playerView);
}
}
@Override
public boolean isInFullScreen() {
if (sliderCallback != null) {
return sliderCallback.isInFullScreen();
}
return false;
}
};
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
String videoUrl = null;
@ -139,16 +137,10 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
aspectRatio,
ResponseBodyUtils.getThumbUrl(media),
loadVideoOnItemClick,
controlsBinding,
videoPlayerCallback);
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
// final float newVol = videoPlayerViewHelper.toggleMute();
// setMuteIcon(newVol);
// Utils.sessionVolumeFull = newVol == 1f;
// });
binding.playerView.setOnClickListener(v -> {
if (sliderCallback != null) {
sliderCallback.onItemClicked(position);
sliderCallback.onItemClicked(position, media, binding.getRoot());
}
});
}
@ -162,62 +154,4 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.releasePlayer();
}
public void resetPlayerTimeline() {
if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.resetTimeline();
}
public void removeCallbacks() {
if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.removeCallbacks();
}
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
}

View File

@ -1,95 +0,0 @@
package awais.instagrabber.adapters.viewholder.comments;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
import awais.instagrabber.databinding.ItemCommentSmallBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Utils;
public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentSmallBinding binding;
public ChildCommentViewHolder(@NonNull final ItemCommentSmallBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final CommentModel comment,
final boolean selected,
final CommentCallback commentCallback) {
if (comment == null) return;
if (commentCallback != null) {
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
}
if (selected) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
} else {
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
}
setupCommentText(comment, commentCallback);
binding.tvDate.setText(comment.getDateTime());
setLiked(comment.getLiked());
setLikes((int) comment.getLikes());
setUser(comment);
}
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
binding.tvComment.clearOnURLClickListeners();
binding.tvComment.clearOnHashtagClickListeners();
binding.tvComment.clearOnMentionClickListeners();
binding.tvComment.clearOnEmailClickListeners();
binding.tvComment.setText(comment.getText());
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onHashtagClick(originalText);
});
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onMentionClick(originalText);
});
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onEmailClick(originalText);
});
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onURLClick(originalText);
});
binding.tvComment.setOnLongClickListener(v -> {
Utils.copyText(itemView.getContext(), comment.getText());
return true;
});
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
}
private void setUser(final CommentModel comment) {
final User profileModel = comment.getProfileModel();
if (profileModel == null) return;
binding.tvUsername.setText(profileModel.getUsername());
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
}
private void setLikes(final int likes) {
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.tvLikes.setText(likesString);
}
public final void setLiked(final boolean liked) {
if (liked) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
}
}
}

View File

@ -1,95 +0,0 @@
package awais.instagrabber.adapters.viewholder.comments;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Utils;
public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentBinding binding;
public ParentCommentViewHolder(@NonNull final ItemCommentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final CommentModel comment,
final boolean selected,
final CommentCallback commentCallback) {
if (comment == null) return;
if (commentCallback != null) {
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
}
if (selected) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
} else {
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
}
setupCommentText(comment, commentCallback);
binding.tvDate.setText(comment.getDateTime());
setLiked(comment.getLiked());
setLikes((int) comment.getLikes());
setUser(comment);
}
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
binding.tvComment.clearOnURLClickListeners();
binding.tvComment.clearOnHashtagClickListeners();
binding.tvComment.clearOnMentionClickListeners();
binding.tvComment.clearOnEmailClickListeners();
binding.tvComment.setText(comment.getText());
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onHashtagClick(originalText);
});
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onMentionClick(originalText);
});
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onEmailClick(originalText);
});
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
final String originalText = autoLinkItem.getOriginalText();
if (commentCallback == null) return;
commentCallback.onURLClick(originalText);
});
binding.tvComment.setOnLongClickListener(v -> {
Utils.copyText(itemView.getContext(), comment.getText());
return true;
});
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
}
private void setUser(final CommentModel comment) {
final User profileModel = comment.getProfileModel();
if (profileModel == null) return;
binding.tvUsername.setText(profileModel.getUsername());
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
}
private void setLikes(final int likes) {
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.tvLikes.setText(likesString);
}
public final void setLiked(final boolean liked) {
if (liked) {
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
}
}
}

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.backends.pipeline.Fresco;
@ -23,6 +22,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
if (fixedHeight == null) return;
final String url = fixedHeight.getWebp();
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
fixedHeight.getHeight(),
fixedHeight.getWidth(),
mediaImageMaxHeight,
@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
);
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0;
final int height = widthHeight.second != null ? widthHeight.second : 0;
final int width = widthHeight.first;
final int height = widthHeight.second;
layoutParams.width = width;
layoutParams.height = height;
binding.ivAnimatedMessage.requestLayout();

View File

@ -6,7 +6,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.drawable.ScalingUtils;
@ -31,6 +30,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils;
@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.setRoundingParams(roundingParams)
.build());
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(),
media.getOriginalWidth(),
mediaImageMaxHeight,
mediaImageMaxWidth
);
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second;
binding.mediaPreview.requestLayout();
binding.mediaPreview.setTag(url);
binding.mediaPreview.setImageURI(url);

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
@ -14,11 +13,11 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmMediaBinding;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
@ -53,16 +52,16 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder {
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE
: View.GONE);
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(),
media.getOriginalWidth(),
mediaImageMaxHeight,
mediaImageMaxWidth
);
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0;
final int width = widthHeight.first;
layoutParams.width = width;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
layoutParams.height = widthHeight.second;
binding.mediaPreview.requestLayout();
binding.bgTime.getLayoutParams().width = width;
binding.bgTime.requestLayout();

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
@ -21,6 +20,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
@ -170,15 +170,15 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE
: View.GONE);
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(),
media.getOriginalWidth(),
mediaImageMaxHeight,
maxWidth
);
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second;
binding.preview.requestLayout();
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
binding.preview.setImageURI(thumbUrl);

View File

@ -5,7 +5,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.drawable.ScalingUtils;
@ -17,12 +16,12 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
.setRoundingParams(roundingParams)
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.build());
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
storyShareMedia.getOriginalHeight(),
storyShareMedia.getOriginalWidth(),
mediaImageMaxHeight,
mediaImageMaxWidth
);
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second;
binding.ivMediaPreview.requestLayout();
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
binding.ivMediaPreview.setImageURI(thumbUrl);

View File

@ -112,6 +112,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
}
public void bind(final int position, final DirectItem item) {
if (item == null) return;
this.item = item;
messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
// Asynchronous binding causes some weird behaviour
@ -123,7 +124,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
setupLongClickListener(position, messageDirection);
}
private void bindBase(final DirectItem item, final MessageDirection messageDirection, final int position) {
private void bindBase(@NonNull final DirectItem item, final MessageDirection messageDirection, final int position) {
final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams();
final DirectItemType itemType = item.getItemType();
setMessageDirectionGravity(messageDirection, containerLayoutParams);
@ -188,7 +189,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
containerLayoutParams.gravity = Gravity.CENTER;
}
private void setMessageInfo(final DirectItem item, final MessageDirection messageDirection) {
private void setMessageInfo(@NonNull final DirectItem item, final MessageDirection messageDirection) {
if (showMessageInfo()) {
binding.messageInfo.setVisibility(View.VISIBLE);
binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
@ -550,6 +551,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
menu.setOnDismissListener(() -> setSelected(false));
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
menu.setOnAddReactionListener(() -> {
menu.dismiss();
itemView.postDelayed(() -> callback.onAddReactionListener(item), 300);
});
menu.show(itemView, location);
}

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.backends.pipeline.Fresco;
@ -16,6 +15,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemXma;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils;
public class DirectItemXmaViewHolder extends DirectItemViewHolder {
@ -43,7 +43,7 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
}
final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
final String url = urlInfo.getUrl();
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
urlInfo.getHeight(),
urlInfo.getWidth(),
mediaImageMaxHeight,
@ -51,8 +51,8 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
);
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0;
final int height = widthHeight.second != null ? widthHeight.second : 0;
final int width = widthHeight.first;
final int height = widthHeight.second;
layoutParams.width = width;
layoutParams.height = height;
binding.ivAnimatedMessage.requestLayout();

View File

@ -55,7 +55,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
private void setupComments(@NonNull final Media feedModel) {
final long commentsCount = feedModel.getCommentCount();
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
bottomBinding.commentsCount.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
bottomBinding.btnComments.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
}
private void setupProfilePic(@NonNull final Media media) {
@ -75,6 +75,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
// final SpannableString spannableString = new SpannableString();
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
final User user = media.getUser();
if (user == null) return;
final String title = "@" + user.getUsername();
topBinding.title.setText(title);
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic));
@ -120,8 +121,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
));
}
else {
} else {
final String locationName = location.getName();
if (TextUtils.isEmpty(locationName)) {
topBinding.location.setVisibility(View.GONE);

View File

@ -45,9 +45,9 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(1);
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
final SliderItemsAdapter adapter = new SliderItemsAdapter(false, new SliderCallbackAdapter() {
@Override
public void onItemClicked(final int position) {
public void onItemClicked(final int position, final Media media, final View view) {
feedItemCallback.onSliderClick(feedModel, position);
}
});

View File

@ -97,7 +97,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
aspectRatio,
ResponseBodyUtils.getThumbUrl(media),
false,
null,
// null,
videoPlayerCallback);
binding.videoPost.thumbnail.post(() -> {
if (media.getOriginalHeight() > 0.8 * Utils.displayMetrics.heightPixels) {

View File

@ -1,268 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.TextUtils;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
private static final String TAG = "CommentsFetcher";
private final String shortCode, endCursor;
private final FetchListener<List<CommentModel>> fetchListener;
public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) {
this.shortCode = shortCode;
this.endCursor = endCursor;
this.fetchListener = fetchListener;
}
@NonNull
@Override
protected List<CommentModel> doInBackground(final Void... voids) {
/*
"https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + "{\"shortcode\":\"" + shortcode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
97b41c52301f77ce508f55e66d17620e -> for comments
51fdd02b67508306ad4484ff574a0b62 -> for child comments
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
*/
final List<CommentModel> commentModels = getParentComments();
if (commentModels != null) {
for (final CommentModel commentModel : commentModels) {
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.size();
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null);
}
}
}
}
return commentModels;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final List<CommentModel> result) {
if (fetchListener != null) fetchListener.onResult(result);
}
@NonNull
private synchronized List<CommentModel> getChildComments(final String commentId) {
final List<CommentModel> commentModels = new ArrayList<>();
String childEndCursor = "";
while (childEndCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else {
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("comment")
.getJSONObject("edge_threaded_comments");
final JSONObject pageInfo = data.getJSONObject("page_info");
childEndCursor = pageInfo.getString("end_cursor");
if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null;
final JSONArray childComments = data.optJSONArray("edges");
if (childComments != null) {
final int length = childComments.length();
for (int i = 0; i < length; ++i) {
final JSONObject childComment = childComments.getJSONObject(i).optJSONObject("node");
if (childComment != null) {
final JSONObject owner = childComment.getJSONObject("owner");
final User user = new User(
owner.optLong(Constants.EXTRAS_ID, 0),
owner.getString(Constants.EXTRAS_USERNAME),
null,
false,
owner.getString("profile_pic_url"),
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
false, false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null,
null, null, null);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
user));
}
}
}
}
conn.disconnect();
} catch (final Exception e) {
// if (logCollector != null)
// logCollector.appendException(e,
// LogCollector.LogFile.ASYNC_COMMENTS_FETCHER,
// "getChildComments",
// new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
if (fetchListener != null) fetchListener.onFailure(e);
break;
}
}
return commentModels;
}
@NonNull
private synchronized List<CommentModel> getParentComments() {
final List<CommentModel> commentModels = new ArrayList<>();
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null;
else {
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("shortcode_media")
.getJSONObject(
"edge_media_to_parent_comment");
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
final String foundEndCursor = pageInfo.optString("end_cursor");
final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor));
// final boolean containsToken = endCursor.contains("bifilter_token");
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) {
// final JSONObject endCursorObject = new JSONObject(endCursor);
// endCursor = endCursorObject.optString("cached_comments_cursor");
//
// if (!Utils.isEmpty(endCursor))
// endCursor = "{\\\"cached_comments_cursor\\\": \\\"" + endCursor + "\\\", ";
// else
// endCursor = "{";
//
// endCursor = endCursor + "\\\"bifilter_token\\\": \\\"" + endCursorObject.getString("bifilter_token") + "\\\"}";
// }
// else if (containsToken) endCursor = null;
final JSONArray comments = parentComments.getJSONArray("edges");
final int commentsLen = comments.length();
for (int i = 0; i < commentsLen; ++i) {
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
final JSONObject owner = comment.getJSONObject("owner");
final User user = new User(
owner.optLong(Constants.EXTRAS_ID, 0),
owner.getString(Constants.EXTRAS_USERNAME),
null,
false,
owner.getString("profile_pic_url"),
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
owner.optBoolean("is_verified"),
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null,
null, null);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
final String commentId = comment.getString(Constants.EXTRAS_ID);
final CommentModel commentModel = new CommentModel(commentId,
comment.getString("text"),
comment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
comment.getBoolean("viewer_has_liked"),
user);
if (i == 0 && !foundEndCursor.contains("tao_cursor"))
commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor);
JSONObject tempJsonObject;
final JSONArray childCommentsArray;
final int childCommentsLen;
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null &&
(childCommentsArray = tempJsonObject.optJSONArray("edges")) != null
&& (childCommentsLen = childCommentsArray.length()) > 0) {
final String childEndCursor;
final boolean childHasNextPage;
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) {
childEndCursor = tempJsonObject.optString("end_cursor");
childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor));
} else {
childEndCursor = null;
childHasNextPage = false;
}
final List<CommentModel> childCommentModels = new ArrayList<>();
for (int j = 0; j < childCommentsLen; ++j) {
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
tempJsonObject = childComment.getJSONObject("owner");
final User childUser = new User(
tempJsonObject.optLong(Constants.EXTRAS_ID, 0),
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
null,
false,
tempJsonObject.getString("profile_pic_url"),
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
tempJsonObject.optBoolean("is_verified"), false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0,
null, null, null, null, null, null);
tempJsonObject = childComment.optJSONObject("edge_liked_by");
childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"),
childUser));
}
childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor);
commentModel.setChildCommentModels(childCommentModels);
}
commentModels.add(commentModel);
}
}
conn.disconnect();
} catch (final Exception e) {
// if (logCollector != null)
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
// new Pair<>("commentModelsList.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (fetchListener != null) fetchListener.onFailure(e);
return null;
}
return commentModels;
}
}

View File

@ -12,9 +12,6 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, Media> {
private static final String TAG = "PostFetcher";
@ -136,9 +133,9 @@ public final class PostFetcher extends AsyncTask<Void, Void, Media> {
return ResponseBodyUtils.parseGraphQLItem(media, null);
}
} catch (Exception e) {
// if (logCollector != null) {
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
// }
// if (logCollector != null) {
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
// }
Log.e(TAG, "Error fetching post", e);
} finally {
if (conn != null) {

View File

@ -0,0 +1,165 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.transition.ChangeBounds;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;
import java.time.Duration;
import awais.instagrabber.customviews.helpers.ChangeText;
import awais.instagrabber.utils.NumberUtils;
public class FormattedNumberTextView extends AppCompatTextView {
private static final String TAG = FormattedNumberTextView.class.getSimpleName();
private static final Transition TRANSITION;
private long number = Long.MIN_VALUE;
private boolean showAbbreviation = true;
private boolean animateChanges = false;
private boolean toggleOnClick = true;
private boolean autoToggleToAbbreviation = true;
private long autoToggleTimeoutMs = Duration.ofSeconds(2).toMillis();
private boolean initDone = false;
static {
final TransitionSet transitionSet = new TransitionSet();
final ChangeText changeText = new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
transitionSet.addTransition(changeText).addTransition(new ChangeBounds());
TRANSITION = transitionSet;
}
public FormattedNumberTextView(@NonNull final Context context) {
super(context);
init();
}
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
init();
}
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
if (initDone) return;
setupClickToggle();
initDone = true;
}
private void setupClickToggle() {
setOnClickListener(null);
}
private OnClickListener getWrappedClickListener(@Nullable final OnClickListener l) {
if (!toggleOnClick) {
return l;
}
return v -> {
toggleAbbreviation();
if (l != null) {
l.onClick(this);
}
};
}
public void setNumber(final long number) {
if (this.number == number) return;
this.number = number;
format();
}
public void clearNumber() {
if (number == Long.MIN_VALUE) return;
number = Long.MIN_VALUE;
format();
}
public void setShowAbbreviation(final boolean showAbbreviation) {
if (this.showAbbreviation && showAbbreviation) return;
this.showAbbreviation = showAbbreviation;
format();
}
public boolean isShowAbbreviation() {
return showAbbreviation;
}
private void toggleAbbreviation() {
if (number == Long.MIN_VALUE) return;
setShowAbbreviation(!showAbbreviation);
}
public void setToggleOnClick(final boolean toggleOnClick) {
this.toggleOnClick = toggleOnClick;
}
public boolean isToggleOnClick() {
return toggleOnClick;
}
public void setAutoToggleToAbbreviation(final boolean autoToggleToAbbreviation) {
this.autoToggleToAbbreviation = autoToggleToAbbreviation;
}
public boolean isAutoToggleToAbbreviation() {
return autoToggleToAbbreviation;
}
public void setAutoToggleTimeoutMs(final long autoToggleTimeoutMs) {
this.autoToggleTimeoutMs = autoToggleTimeoutMs;
}
public long getAutoToggleTimeoutMs() {
return autoToggleTimeoutMs;
}
public void setAnimateChanges(final boolean animateChanges) {
this.animateChanges = animateChanges;
}
public boolean isAnimateChanges() {
return animateChanges;
}
@Override
public void setOnClickListener(@Nullable final OnClickListener l) {
super.setOnClickListener(getWrappedClickListener(l));
}
private void format() {
post(() -> {
if (animateChanges) {
try {
TransitionManager.beginDelayedTransition((ViewGroup) getParent(), TRANSITION);
} catch (Exception e) {
Log.e(TAG, "format: ", e);
}
}
if (number == Long.MIN_VALUE) {
setText(null);
return;
}
if (showAbbreviation) {
setText(NumberUtils.abbreviate(number));
return;
}
setText(String.valueOf(number));
if (autoToggleToAbbreviation) {
getHandler().postDelayed(() -> setShowAbbreviation(true), autoToggleTimeoutMs);
}
});
}
}

View File

@ -0,0 +1,75 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;
import awais.instagrabber.R;
@Navigator.Name("fragment")
public class FragmentNavigatorWithDefaultAnimations extends FragmentNavigator {
private final NavOptions emptyNavOptions = new NavOptions.Builder().build();
// private final NavOptions defaultNavOptions = new NavOptions.Builder()
// .setEnterAnim(R.animator.nav_default_enter_anim)
// .setExitAnim(R.animator.nav_default_exit_anim)
// .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
// .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
// .build();
private final NavOptions defaultNavOptions = new NavOptions.Builder()
.setEnterAnim(R.anim.slide_in_right)
.setExitAnim(R.anim.slide_out_left)
.setPopEnterAnim(android.R.anim.slide_in_left)
.setPopExitAnim(android.R.anim.slide_out_right)
.build();
public FragmentNavigatorWithDefaultAnimations(@NonNull final Context context,
@NonNull final FragmentManager manager,
final int containerId) {
super(context, manager, containerId);
}
@Nullable
@Override
public NavDestination navigate(@NonNull final Destination destination,
@Nullable final Bundle args,
@Nullable final NavOptions navOptions,
@Nullable final Navigator.Extras navigatorExtras) {
// this will try to fill in empty animations with defaults when no shared element transitions are set
// https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element
final boolean shouldUseTransitionsInstead = navigatorExtras != null;
final NavOptions navOptions1 = shouldUseTransitionsInstead ? navOptions : fillEmptyAnimationsWithDefaults(navOptions);
return super.navigate(destination, args, navOptions1, navigatorExtras);
}
private NavOptions fillEmptyAnimationsWithDefaults(@Nullable final NavOptions navOptions) {
if (navOptions == null) {
return defaultNavOptions;
}
return copyNavOptionsWithDefaultAnimations(navOptions);
}
@NonNull
private NavOptions copyNavOptionsWithDefaultAnimations(@NonNull final NavOptions navOptions) {
return new NavOptions.Builder()
.setLaunchSingleTop(navOptions.shouldLaunchSingleTop())
.setPopUpTo(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive())
.setEnterAnim(navOptions.getEnterAnim() == emptyNavOptions.getEnterAnim()
? defaultNavOptions.getEnterAnim() : navOptions.getEnterAnim())
.setExitAnim(navOptions.getExitAnim() == emptyNavOptions.getExitAnim()
? defaultNavOptions.getExitAnim() : navOptions.getExitAnim())
.setPopEnterAnim(navOptions.getPopEnterAnim() == emptyNavOptions.getPopEnterAnim()
? defaultNavOptions.getPopEnterAnim() : navOptions.getPopEnterAnim())
.setPopExitAnim(navOptions.getPopExitAnim() == emptyNavOptions.getPopExitAnim()
? defaultNavOptions.getPopExitAnim() : navOptions.getPopExitAnim())
.build();
}
}

View File

@ -0,0 +1,60 @@
package awais.instagrabber.customviews;
import android.os.Bundle;
import androidx.annotation.NavigationRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.NavController;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;
import androidx.navigation.fragment.NavHostFragment;
public class NavHostFragmentWithDefaultAnimations extends NavHostFragment {
private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId";
private static final String KEY_START_DESTINATION_ARGS =
"android-support-nav:fragment:startDestinationArgs";
private static final String KEY_NAV_CONTROLLER_STATE =
"android-support-nav:fragment:navControllerState";
private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost";
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId) {
return create(graphResId, null);
}
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId,
@Nullable Bundle startDestinationArgs) {
Bundle b = null;
if (graphResId != 0) {
b = new Bundle();
b.putInt(KEY_GRAPH_ID, graphResId);
}
if (startDestinationArgs != null) {
if (b == null) {
b = new Bundle();
}
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
}
final NavHostFragmentWithDefaultAnimations result = new NavHostFragmentWithDefaultAnimations();
if (b != null) {
result.setArguments(b);
}
return result;
}
@NonNull
@Override
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId());
}
@Override
protected void onCreateNavController(@NonNull final NavController navController) {
super.onCreateNavController(navController);
navController.getNavigatorProvider()
.addNavigator(new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId()));
}
}

View File

@ -3,6 +3,8 @@ package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -25,6 +27,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
@ -60,14 +63,17 @@ public class PostsRecyclerView extends RecyclerView {
private FeedAdapterV2.FeedItemCallback feedItemCallback;
private boolean shouldScrollToTop;
private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
private Function<ViewGroup, View> headerViewCreator;
private Function<View, Void> headerBinder;
private boolean refresh = true;
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
@Override
public void onResult(final List<Media> result) {
final int currentPage = lazyLoader.getCurrentPage();
if (currentPage == 0) {
if (refresh) {
refresh = false;
mediaViewModel.getList().postValue(result);
shouldScrollToTop = true;
dispatchFetchStatus();
@ -192,22 +198,25 @@ public class PostsRecyclerView extends RecyclerView {
}
private void initSelf() {
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
mediaViewModel.getList().observe(lifeCycleOwner, list -> {
if (list.size() <= 0) return;
feedAdapter.submitList(list, () -> {
// postDelayed(this::fetchMoreIfPossible, 1000);
if (!shouldScrollToTop) return;
smoothScrollToPosition(0);
shouldScrollToTop = false;
});
});
try {
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
} catch (Exception e) {
Log.e(TAG, "initSelf: ", e);
}
if (mediaViewModel == null) return;
mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
// postDelayed(this::fetchMoreIfPossible, 1000);
if (!shouldScrollToTop) return;
shouldScrollToTop = false;
post(() -> smoothScrollToPosition(0));
}));
postFetcher = new PostFetcher(postFetchService, fetchListener);
if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration);
}
setHasFixedSize(true);
setNestedScrollingEnabled(true);
setItemAnimator(null);
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
if (postFetcher.hasMore()) {
postFetcher.fetch();
@ -311,11 +320,12 @@ public class PostsRecyclerView extends RecyclerView {
}
public void refresh() {
refresh = true;
if (lazyLoader != null) {
lazyLoader.resetState();
}
if (postFetcher != null) {
mediaViewModel.getList().postValue(Collections.emptyList());
// mediaViewModel.getList().postValue(Collections.emptyList());
postFetcher.reset();
postFetcher.fetch();
}

View File

@ -70,6 +70,9 @@ public final class ProfilePicView extends CircularImageView {
case SMALL:
dimenRes = R.dimen.profile_pic_size_small;
break;
case SMALLER:
dimenRes = R.dimen.profile_pic_size_smaller;
break;
case TINY:
dimenRes = R.dimen.profile_pic_size_tiny;
break;
@ -113,7 +116,8 @@ public final class ProfilePicView extends CircularImageView {
TINY(0),
SMALL(1),
REGULAR(2),
LARGE(3);
LARGE(3),
SMALLER(4);
private final int value;
private static final Map<Integer, Size> map = new HashMap<>();

View File

@ -1,10 +1,12 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.text.InputFilter;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.emoji.widget.EmojiTextViewHelper;
import java.util.ArrayList;
import java.util.List;
@ -23,6 +25,8 @@ public class RamboTextViewV2 extends AutoLinkTextView {
private final List<OnURLClickListener> onURLClickListeners = new ArrayList<>();
private final List<OnEmailClickListener> onEmailClickListeners = new ArrayList<>();
private EmojiTextViewHelper emojiTextViewHelper;
public RamboTextViewV2(@NonNull final Context context,
@Nullable final AttributeSet attrs) {
super(context, attrs);
@ -30,6 +34,7 @@ public class RamboTextViewV2 extends AutoLinkTextView {
}
private void init() {
getEmojiTextViewHelper().updateTransformationMethod();
addAutoLinkMode(MODE_HASHTAG.INSTANCE, MODE_MENTION.INSTANCE, MODE_EMAIL.INSTANCE, MODE_URL.INSTANCE);
onAutoLinkClick(autoLinkItem -> {
final Mode mode = autoLinkItem.getMode();
@ -57,6 +62,26 @@ public class RamboTextViewV2 extends AutoLinkTextView {
}
}
});
onAutoLinkLongClick(autoLinkItem -> {});
}
@Override
public void setFilters(InputFilter[] filters) {
super.setFilters(getEmojiTextViewHelper().getFilters(filters));
}
@Override
public void setAllCaps(boolean allCaps) {
super.setAllCaps(allCaps);
getEmojiTextViewHelper().setAllCaps(allCaps);
}
private EmojiTextViewHelper getEmojiTextViewHelper() {
if (emojiTextViewHelper == null) {
emojiTextViewHelper = new EmojiTextViewHelper(this);
}
return emojiTextViewHelper;
}
public void addOnMentionClickListener(final OnMentionClickListener onMentionClickListener) {

View File

@ -0,0 +1,95 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.emoji.widget.EmojiAppCompatTextView;
import awais.instagrabber.R;
/**
* https://stackoverflow.com/a/31916731
*/
public class TextViewDrawableSize extends EmojiAppCompatTextView {
private int mDrawableWidth;
private int mDrawableHeight;
private boolean calledFromInit = false;
public TextViewDrawableSize(final Context context) {
this(context, null);
}
public TextViewDrawableSize(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public TextViewDrawableSize(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(@NonNull final Context context, final AttributeSet attrs, final int defStyleAttr) {
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextViewDrawableSize, defStyleAttr, 0);
try {
mDrawableWidth = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableWidth, -1);
mDrawableHeight = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableHeight, -1);
} finally {
array.recycle();
}
if (mDrawableWidth > 0 || mDrawableHeight > 0) {
initCompoundDrawableSize();
}
}
private void initCompoundDrawableSize() {
final Drawable[] drawables = getCompoundDrawablesRelative();
for (Drawable drawable : drawables) {
if (drawable == null) {
continue;
}
final Rect realBounds = drawable.getBounds();
float scaleFactor = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
float drawableWidth = drawable.getIntrinsicWidth();
float drawableHeight = drawable.getIntrinsicHeight();
if (mDrawableWidth > 0) {
// save scale factor of image
if (drawableWidth > mDrawableWidth) {
drawableWidth = mDrawableWidth;
drawableHeight = drawableWidth * scaleFactor;
}
}
if (mDrawableHeight > 0) {
// save scale factor of image
if (drawableHeight > mDrawableHeight) {
drawableHeight = mDrawableHeight;
drawableWidth = drawableHeight / scaleFactor;
}
}
realBounds.right = realBounds.left + Math.round(drawableWidth);
realBounds.bottom = realBounds.top + Math.round(drawableHeight);
drawable.setBounds(realBounds);
}
setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
}
public void setCompoundDrawablesRelativeWithSize(@Nullable final Drawable start,
@Nullable final Drawable top,
@Nullable final Drawable end,
@Nullable final Drawable bottom) {
setCompoundDrawablesRelative(start, top, end, bottom);
initCompoundDrawableSize();
}
}

View File

@ -9,20 +9,20 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.ViewUtils;
public class Tooltip extends AppCompatTextView {
private View anchor;
private ViewPropertyAnimator animator;
private boolean showing;
private final AppExecutors appExecutors;
private final AppExecutors appExecutors = AppExecutors.getInstance();
private final Runnable dismissRunnable = () -> {
animator = animate().alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
@ -33,7 +33,7 @@ public class Tooltip extends AppCompatTextView {
animator.start();
};
public Tooltip(Context context, ViewGroup parentView, int backgroundColor, int textColor) {
public Tooltip(@NonNull Context context, @NonNull ViewGroup parentView, int backgroundColor, int textColor) {
super(context);
setBackgroundDrawable(ViewUtils.createRoundRectDrawable(Utils.convertDpToPx(3), backgroundColor));
setTextColor(textColor);
@ -43,7 +43,6 @@ public class Tooltip extends AppCompatTextView {
parentView.addView(this, ViewUtils.createFrame(
ViewUtils.WRAP_CONTENT, ViewUtils.WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3));
setVisibility(GONE);
appExecutors = AppExecutors.getInstance();
}
@Override

View File

@ -0,0 +1,77 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.AttributeSet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatTextView;
import awais.instagrabber.R;
import awais.instagrabber.utils.Utils;
public class UsernameTextView extends AppCompatTextView {
private static final String TAG = UsernameTextView.class.getSimpleName();
private final int drawableSize = Utils.convertDpToPx(24);
private boolean verified;
private VerticalImageSpan verifiedSpan;
public UsernameTextView(@NonNull final Context context) {
this(context, null);
}
public UsernameTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public UsernameTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
try {
final Drawable verifiedDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.verified);
final Drawable drawable = verifiedDrawable.mutate();
drawable.setBounds(0, 0, drawableSize, drawableSize);
verifiedSpan = new VerticalImageSpan(drawable);
} catch (Exception e) {
Log.e(TAG, "init: ", e);
}
}
public void setUsername(final CharSequence username) {
setUsername(username, false);
}
public void setUsername(final CharSequence username, final boolean verified) {
this.verified = verified;
final SpannableStringBuilder sb = new SpannableStringBuilder(username);
if (verified) {
try {
if (verifiedSpan != null) {
sb.append(" ");
sb.setSpan(verifiedSpan, sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} catch (Exception e) {
Log.e(TAG, "bind: ", e);
}
}
super.setText(sb);
}
public boolean isVerified() {
return verified;
}
public void setVerified(final boolean verified) {
setUsername(getText(), verified);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.customviews;
import com.google.android.exoplayer2.ui.StyledPlayerView;
public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback {
@Override
public void onThumbnailLoaded() {}
@ -18,4 +20,12 @@ public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPl
@Override
public void onRelease() {}
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
@Override
public boolean isInFullScreen() {
return false;
}
}

View File

@ -1,123 +1,70 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.PopupMenu;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.material.slider.LabelFormatter;
import com.google.android.material.slider.Slider;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import awais.instagrabber.R;
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.utils.TextUtils;
import static com.google.android.exoplayer2.C.TIME_UNSET;
import static com.google.android.exoplayer2.Player.STATE_ENDED;
import static com.google.android.exoplayer2.Player.STATE_IDLE;
import static com.google.android.exoplayer2.Player.STATE_READY;
import awais.instagrabber.utils.Utils;
public class VideoPlayerViewHelper implements Player.EventListener {
private static final String TAG = "VideoPlayerViewHelper";
private static final long INITIAL_DELAY = 0;
private static final long RECURRING_DELAY = 60;
private static final String TAG = VideoPlayerViewHelper.class.getSimpleName();
private final Context context;
private final awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding binding;
private final LayoutVideoPlayerWithThumbnailBinding binding;
private final float initialVolume;
private final float thumbnailAspectRatio;
private final String thumbnailUrl;
private final boolean loadPlayerOnClick;
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
private final VideoPlayerCallback videoPlayerCallback;
private final String videoUrl;
private final DefaultDataSourceFactory dataSourceFactory;
private SimpleExoPlayer player;
private PopupMenu speedPopup;
private PositionCheckRunnable positionChecker;
private Handler positionUpdateHandler;
private AppCompatImageButton mute;
private final Player.EventListener listener = new Player.EventListener() {
@Override
public void onPlaybackStateChanged(final int state) {
switch (state) {
case Player.STATE_BUFFERING:
case STATE_IDLE:
case STATE_ENDED:
positionUpdateHandler.removeCallbacks(positionChecker);
return;
case STATE_READY:
setupTimeline();
positionUpdateHandler.postDelayed(positionChecker, INITIAL_DELAY);
break;
}
}
@Override
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
updatePlayPauseDrawable(playWhenReady);
if (positionUpdateHandler == null || positionChecker == null) return;
if (playWhenReady) {
positionUpdateHandler.removeCallbacks(positionChecker);
positionUpdateHandler.postDelayed(positionChecker, INITIAL_DELAY);
}
}
};
private final AudioListener audioListener = new AudioListener() {
@Override
public void onVolumeChanged(final float volume) {
updateMuteIcon(volume);
}
};
private final Slider.OnChangeListener onChangeListener = (slider, value, fromUser) -> {
if (!fromUser) return;
long actualValue = (long) value;
if (actualValue < 0) {
actualValue = 0;
} else if (actualValue > player.getDuration()) {
actualValue = player.getDuration();
}
player.seekTo(actualValue);
};
private final View.OnClickListener onClickListener = v -> player.setPlayWhenReady(!player.getPlayWhenReady());
private final LabelFormatter labelFormatter = value -> TextUtils.millisToTimeString((long) value);
private final View.OnClickListener muteOnClickListener = v -> toggleMute();
private final View.OnClickListener rewOnClickListener = v -> {
final long positionMs = player.getCurrentPosition() - 5000;
player.seekTo(positionMs < 0 ? 0 : positionMs);
};
private final View.OnClickListener ffOnClickListener = v -> {
long positionMs = player.getCurrentPosition() + 5000;
long duration = player.getDuration();
if (duration == TIME_UNSET) {
duration = 0;
}
player.seekTo(Math.min(positionMs, duration));
};
private final View.OnClickListener showMenu = this::showMenu;
private Object layoutManager;
public VideoPlayerViewHelper(@NonNull final Context context,
@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
@ -126,7 +73,6 @@ public class VideoPlayerViewHelper implements Player.EventListener {
final float thumbnailAspectRatio,
final String thumbnailUrl,
final boolean loadPlayerOnClick,
final LayoutExoCustomControlsBinding controlsBinding,
final VideoPlayerCallback videoPlayerCallback) {
this.context = context;
this.binding = binding;
@ -134,7 +80,6 @@ public class VideoPlayerViewHelper implements Player.EventListener {
this.thumbnailAspectRatio = thumbnailAspectRatio;
this.thumbnailUrl = thumbnailUrl;
this.loadPlayerOnClick = loadPlayerOnClick;
this.controlsBinding = controlsBinding;
this.videoPlayerCallback = videoPlayerCallback;
this.videoUrl = videoUrl;
this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram");
@ -151,40 +96,43 @@ public class VideoPlayerViewHelper implements Player.EventListener {
}
});
setThumbnail();
setupControls();
}
private void setThumbnail() {
binding.thumbnail.setAspectRatio(thumbnailAspectRatio);
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
ImageRequest thumbnailRequest = null;
if (thumbnailUrl != null) {
thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build();
}
final PipelineDraweeControllerBuilder builder = Fresco
.newDraweeControllerBuilder()
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
})
.setImageRequest(thumbnailRequest)
.build();
binding.thumbnail.setController(controller);
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded();
}
}
});
if (thumbnailRequest != null) {
builder.setImageRequest(thumbnailRequest);
}
binding.thumbnail.setController(builder.build());
}
private void loadPlayer() {
if (videoUrl == null) return;
if (binding.root.getDisplayedChild() == 0) {
binding.root.showNext();
if (binding.getRoot().getDisplayedChild() == 0) {
binding.getRoot().showNext();
}
if (videoPlayerCallback != null) {
videoPlayerCallback.onPlayerViewLoaded();
@ -193,15 +141,15 @@ public class VideoPlayerViewHelper implements Player.EventListener {
if (player != null) {
player.release();
}
final ViewGroup.LayoutParams playerViewLayoutParams = binding.playerView.getLayoutParams();
if (playerViewLayoutParams.height > Utils.displayMetrics.heightPixels * 0.8) {
playerViewLayoutParams.height = (int) (Utils.displayMetrics.heightPixels * 0.8);
}
player = new SimpleExoPlayer.Builder(context)
.setLooper(Looper.getMainLooper())
.build();
positionUpdateHandler = new Handler();
positionChecker = new PositionCheckRunnable(positionUpdateHandler,
player,
controlsBinding.timeline,
controlsBinding.fromTime);
player.addListener(this);
player.addAudioListener(audioListener);
player.setVolume(initialVolume);
player.setPlayWhenReady(true);
player.setRepeatMode(Player.REPEAT_MODE_ALL);
@ -209,123 +157,116 @@ public class VideoPlayerViewHelper implements Player.EventListener {
final MediaItem mediaItem = MediaItem.fromUri(videoUrl);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
player.setMediaSource(mediaSource);
setupControls();
player.prepare();
binding.playerView.setPlayer(player);
binding.playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
binding.playerView.setShowNextButton(false);
binding.playerView.setShowPreviousButton(false);
binding.playerView.setControllerOnFullScreenModeChangedListener(isFullScreen -> {
if (videoPlayerCallback == null) return;
videoPlayerCallback.onFullScreenModeChanged(isFullScreen, binding.playerView);
});
setupControllerView();
}
private void setupControls() {
if (controlsBinding == null) return;
binding.playerView.setUseController(false);
if (player == null) {
enableControls(false);
// controlsBinding.playPause.setEnabled(true);
// controlsBinding.playPause.setOnClickListener(new NoPlayerPlayPauseClickListener(binding.thumbnailParent));
private void setupControllerView() {
try {
final StyledPlayerControlView controllerView = getStyledPlayerControlView();
if (controllerView == null) return;
layoutManager = setControlViewLayoutManager(controllerView);
if (videoPlayerCallback != null && videoPlayerCallback.isInFullScreen()) {
setControllerViewToFullScreenMode(controllerView);
}
final ViewGroup exoBasicControls = controllerView.findViewById(R.id.exo_basic_controls);
if (exoBasicControls == null) return;
mute = new AppCompatImageButton(context);
final Resources resources = context.getResources();
if (resources == null) return;
final int width = resources.getDimensionPixelSize(R.dimen.exo_small_icon_width);
final int height = resources.getDimensionPixelSize(R.dimen.exo_small_icon_height);
final int margin = resources.getDimensionPixelSize(R.dimen.exo_small_icon_horizontal_margin);
final int paddingHorizontal = resources.getDimensionPixelSize(R.dimen.exo_small_icon_padding_horizontal);
final int paddingVertical = resources.getDimensionPixelSize(R.dimen.exo_small_icon_padding_vertical);
final ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(width, height);
layoutParams.setMargins(margin, 0, margin, 0);
mute.setLayoutParams(layoutParams);
mute.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
mute.setScaleType(ImageView.ScaleType.FIT_XY);
mute.setBackgroundResource(Utils.getAttrResId(context, android.R.attr.selectableItemBackground));
mute.setImageTintList(ColorStateList.valueOf(resources.getColor(R.color.white)));
updateMuteIcon(player.getVolume());
exoBasicControls.addView(mute, 0);
mute.setOnClickListener(muteOnClickListener);
} catch (Exception e) {
Log.e(TAG, "loadPlayer: ", e);
}
}
@Nullable
private Object setControlViewLayoutManager(@NonNull final StyledPlayerControlView controllerView)
throws NoSuchFieldException, IllegalAccessException {
final Field controlViewLayoutManagerField = controllerView.getClass().getDeclaredField("controlViewLayoutManager");
controlViewLayoutManagerField.setAccessible(true);
return controlViewLayoutManagerField.get(controllerView);
}
private void setControllerViewToFullScreenMode(@NonNull final StyledPlayerControlView controllerView)
throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// Exoplayer doesn't expose the fullscreen state, so using reflection
final Field fullScreenButtonField = controllerView.getClass().getDeclaredField("fullScreenButton");
fullScreenButtonField.setAccessible(true);
final ImageView fullScreenButton = (ImageView) fullScreenButtonField.get(controllerView);
final Field isFullScreen = controllerView.getClass().getDeclaredField("isFullScreen");
isFullScreen.setAccessible(true);
isFullScreen.set(controllerView, true);
final Method updateFullScreenButtonForState = controllerView
.getClass()
.getDeclaredMethod("updateFullScreenButtonForState", ImageView.class, boolean.class);
updateFullScreenButtonForState.setAccessible(true);
updateFullScreenButtonForState.invoke(controllerView, fullScreenButton, true);
}
@Nullable
private StyledPlayerControlView getStyledPlayerControlView() throws NoSuchFieldException, IllegalAccessException {
final Field controller = binding.playerView.getClass().getDeclaredField("controller");
controller.setAccessible(true);
return (StyledPlayerControlView) controller.get(binding.playerView);
}
@Override
public void onTracksChanged(@NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
if (trackGroups.isEmpty()) {
setHasAudio(false);
return;
}
enableControls(true);
updatePlayPauseDrawable(player.getPlayWhenReady());
updateMuteIcon(player.getVolume());
player.addListener(listener);
player.addAudioListener(audioListener);
controlsBinding.timeline.addOnChangeListener(onChangeListener);
controlsBinding.timeline.setLabelFormatter(labelFormatter);
controlsBinding.playPause.setOnClickListener(onClickListener);
controlsBinding.mute.setOnClickListener(muteOnClickListener);
controlsBinding.rewWithAmount.setOnClickListener(rewOnClickListener);
controlsBinding.ffWithAmount.setOnClickListener(ffOnClickListener);
controlsBinding.speed.setOnClickListener(showMenu);
}
private void setupTimeline() {
final long duration = player.getDuration();
controlsBinding.timeline.setEnabled(true);
controlsBinding.timeline.setValueFrom(0);
controlsBinding.timeline.setValueTo(duration);
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0));
controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration));
}
private void enableControls(final boolean enable) {
controlsBinding.speed.setEnabled(enable);
controlsBinding.speed.setClickable(enable);
controlsBinding.mute.setEnabled(enable);
controlsBinding.mute.setClickable(enable);
controlsBinding.ffWithAmount.setEnabled(enable);
controlsBinding.ffWithAmount.setClickable(enable);
controlsBinding.rewWithAmount.setEnabled(enable);
controlsBinding.rewWithAmount.setClickable(enable);
controlsBinding.fromTime.setEnabled(enable);
controlsBinding.toTime.setEnabled(enable);
controlsBinding.playPause.setEnabled(enable);
controlsBinding.playPause.setClickable(enable);
controlsBinding.timeline.setEnabled(enable);
}
public void showMenu(View anchor) {
PopupMenu popup = getPopupMenu(anchor);
popup.show();
}
@NonNull
private PopupMenu getPopupMenu(final View anchor) {
if (speedPopup != null) {
return speedPopup;
}
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle);
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer);
speedPopup = new PopupMenu(themeWrapper, anchor);
speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu());
speedPopup.setOnMenuItemClickListener(item -> {
float nextSpeed;
int textResId;
int itemId = item.getItemId();
if (itemId == R.id.pt_two_five_x) {
nextSpeed = 0.25f;
textResId = R.string.pt_two_five_x;
} else if (itemId == R.id.pt_five_x) {
nextSpeed = 0.5f;
textResId = R.string.pt_five_x;
} else if (itemId == R.id.pt_seven_five_x) {
nextSpeed = 0.75f;
textResId = R.string.pt_seven_five_x;
} else if (itemId == R.id.one_x) {
nextSpeed = 1f;
textResId = R.string.one_x;
} else if (itemId == R.id.one_pt_two_five_x) {
nextSpeed = 1.25f;
textResId = R.string.one_pt_two_five_x;
} else if (itemId == R.id.one_pt_five_x) {
nextSpeed = 1.5f;
textResId = R.string.one_pt_five_x;
} else if (itemId == R.id.two_x) {
nextSpeed = 2f;
textResId = R.string.two_x;
} else {
nextSpeed = 1;
textResId = R.string.one_x;
boolean hasAudio = false;
for (int i = 0; i < trackGroups.length; i++) {
for (int g = 0; g < trackGroups.get(i).length; g++) {
final String sampleMimeType = trackGroups.get(i).getFormat(g).sampleMimeType;
if (sampleMimeType != null && sampleMimeType.contains("audio")) {
hasAudio = true;
break;
}
}
player.setPlaybackParameters(new PlaybackParameters(nextSpeed));
controlsBinding.speed.setText(textResId);
return true;
});
return speedPopup;
}
setHasAudio(hasAudio);
}
private void setHasAudio(final boolean hasAudio) {
if (mute == null) return;
mute.setEnabled(hasAudio);
mute.setAlpha(hasAudio ? 1f : 0.5f);
updateMuteIcon(hasAudio ? 1f : 0f);
}
private void updateMuteIcon(final float volume) {
if (mute == null) return;
if (volume == 0) {
controlsBinding.mute.setIconResource(R.drawable.ic_volume_off_24_states);
mute.setImageResource(R.drawable.ic_volume_off_24);
return;
}
controlsBinding.mute.setIconResource(R.drawable.ic_volume_up_24_states);
}
private void updatePlayPauseDrawable(final boolean playWhenReady) {
if (playWhenReady) {
controlsBinding.playPause.setIconResource(R.drawable.ic_pause_24);
return;
}
controlsBinding.playPause.setIconResource(R.drawable.ic_play_states);
mute.setImageResource(R.drawable.ic_volume_up_24);
}
@Override
@ -339,25 +280,24 @@ public class VideoPlayerViewHelper implements Player.EventListener {
}
@Override
public void onPlayerError(final ExoPlaybackException error) {
public void onPlayerError(@NonNull final ExoPlaybackException error) {
Log.e(TAG, "onPlayerError", error);
}
public float toggleMute() {
if (player == null) return 0;
private void toggleMute() {
if (player == null) return;
if (layoutManager != null) {
try {
final Method resetHideCallbacks = layoutManager.getClass().getDeclaredMethod("resetHideCallbacks");
resetHideCallbacks.invoke(layoutManager);
} catch (Exception e) {
Log.e(TAG, "toggleMute: ", e);
}
}
final float vol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(vol);
return vol;
}
// public void togglePlayback() {
// if (player == null) return;
// final int playbackState = player.getPlaybackState();
// if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) return;
// final boolean playWhenReady = player.getPlayWhenReady();
// player.setPlayWhenReady(!playWhenReady);
// }
public void releasePlayer() {
if (videoPlayerCallback != null) {
videoPlayerCallback.onRelease();
@ -366,84 +306,12 @@ public class VideoPlayerViewHelper implements Player.EventListener {
player.release();
player = null;
}
if (positionUpdateHandler != null) {
if (positionChecker != null) {
positionUpdateHandler.removeCallbacks(positionChecker);
positionChecker = null;
}
positionUpdateHandler = null;
}
}
public void pause() {
if (player != null) {
player.pause();
}
if (positionUpdateHandler != null) {
if (positionChecker != null) {
positionUpdateHandler.removeCallbacks(positionChecker);
}
}
}
public void resetTimeline() {
if (player == null) {
enableControls(false);
return;
}
setupTimeline();
final long currentPosition = player.getCurrentPosition();
controlsBinding.timeline.setValue(Math.min(currentPosition, player.getDuration()));
setupControls();
}
public void removeCallbacks() {
if (player != null) {
player.removeListener(listener);
player.removeAudioListener(audioListener);
}
controlsBinding.timeline.removeOnChangeListener(onChangeListener);
controlsBinding.timeline.setLabelFormatter(null);
controlsBinding.playPause.setOnClickListener(null);
controlsBinding.mute.setOnClickListener(null);
controlsBinding.rewWithAmount.setOnClickListener(null);
controlsBinding.ffWithAmount.setOnClickListener(null);
controlsBinding.speed.setOnClickListener(null);
}
private static class PositionCheckRunnable implements Runnable {
private final Handler positionUpdateHandler;
private final SimpleExoPlayer player;
private final Slider timeline;
private final AppCompatTextView fromTime;
public PositionCheckRunnable(final Handler positionUpdateHandler,
final SimpleExoPlayer simpleExoPlayer,
final Slider slider,
final AppCompatTextView fromTime) {
this.positionUpdateHandler = positionUpdateHandler;
this.player = simpleExoPlayer;
this.timeline = slider;
this.fromTime = fromTime;
}
@Override
public void run() {
if (positionUpdateHandler == null) return;
positionUpdateHandler.removeCallbacks(this);
if (player == null) return;
final long currentPosition = player.getCurrentPosition();
final long duration = player.getDuration();
if (duration == TIME_UNSET) {
timeline.setValueFrom(0);
timeline.setValueTo(0);
timeline.setEnabled(false);
return;
}
timeline.setValue(Math.min(currentPosition, duration));
fromTime.setText(TextUtils.millisToTimeString(currentPosition));
positionUpdateHandler.postDelayed(this, RECURRING_DELAY);
}
}
public interface VideoPlayerCallback {
@ -458,5 +326,9 @@ public class VideoPlayerViewHelper implements Player.EventListener {
void onPause();
void onRelease();
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
boolean isInFullScreen();
}
}

View File

@ -228,7 +228,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
public void setZoomingEnabled(boolean zoomingEnabled) {
mZoomingEnabled = zoomingEnabled;
mZoomableController.setEnabled(false);
mZoomableController.setEnabled(zoomingEnabled);
}
/**

View File

@ -0,0 +1,100 @@
package awais.instagrabber.customviews.emoji;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.utils.Utils;
public class EmojiBottomSheetDialog extends BottomSheetDialogFragment {
public static final String TAG = EmojiBottomSheetDialog.class.getSimpleName();
private RecyclerView grid;
private EmojiPicker.OnEmojiClickListener callback;
@NonNull
public static EmojiBottomSheetDialog newInstance() {
// Bundle args = new Bundle();
// fragment.setArguments(args);
return new EmojiBottomSheetDialog();
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
final Context context = getContext();
if (context == null) return null;
grid = new RecyclerView(context);
return grid;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomSheetInternal.requestLayout();
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
final Fragment parentFragment = getParentFragment();
if (parentFragment instanceof EmojiPicker.OnEmojiClickListener) {
callback = (EmojiPicker.OnEmojiClickListener) parentFragment;
}
}
@Override
public void onDestroyView() {
grid = null;
super.onDestroyView();
}
private void init() {
final Context context = getContext();
if (context == null) return;
final GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 9);
grid.setLayoutManager(gridLayoutManager);
grid.setHasFixedSize(true);
grid.setClipToPadding(false);
grid.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(8)));
final EmojiGridAdapter adapter = new EmojiGridAdapter(null, (view, emoji) -> {
if (callback != null) {
callback.onClick(view, emoji);
}
dismiss();
}, null);
grid.setAdapter(adapter);
}
}

View File

@ -43,7 +43,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
private final EmojiVariantManager emojiVariantManager;
private final AppExecutors appExecutors;
public EmojiGridAdapter(@NonNull final EmojiCategoryType emojiCategoryType,
public EmojiGridAdapter(final EmojiCategoryType emojiCategoryType,
final OnEmojiClickListener onEmojiClickListener,
final OnEmojiLongClickListener onEmojiLongClickListener) {
this.onEmojiClickListener = onEmojiClickListener;
@ -55,6 +55,11 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
emojiVariantManager = EmojiVariantManager.getInstance();
appExecutors = AppExecutors.getInstance();
setHasStableIds(true);
if (emojiCategoryType == null) {
// show all if type is null
differ.submitList(ImmutableList.copyOf(emojiParser.getAllEmojis().values()));
return;
}
final EmojiCategory emojiCategory = categoryMap.get(emojiCategoryType);
if (emojiCategory == null) {
differ.submitList(Collections.emptyList());
@ -105,7 +110,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
}
public static class EmojiViewHolder extends RecyclerView.ViewHolder {
private final AppExecutors appExecutors = AppExecutors.getInstance();
// private final AppExecutors appExecutors = AppExecutors.getInstance();
private final ItemEmojiGridBinding binding;
private final OnEmojiClickListener onEmojiClickListener;
private final OnEmojiLongClickListener onEmojiLongClickListener;
@ -123,17 +128,17 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
binding.image.setImageDrawable(null);
binding.indicator.setVisibility(View.GONE);
itemView.setOnLongClickListener(null);
itemView.post(() -> {
binding.image.setImageDrawable(emoji.getDrawable());
final boolean hasVariants = !parent.getVariants().isEmpty();
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
if (onEmojiClickListener != null) {
itemView.setOnClickListener(v -> onEmojiClickListener.onClick(v, emoji));
}
if (hasVariants && onEmojiLongClickListener != null) {
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
}
});
// itemView.post(() -> {
binding.image.setImageDrawable(emoji.getDrawable());
final boolean hasVariants = !parent.getVariants().isEmpty();
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
if (onEmojiClickListener != null) {
itemView.setOnClickListener(v -> onEmojiClickListener.onClick(v, emoji));
}
if (hasVariants && onEmojiLongClickListener != null) {
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
}
// });
}
}

View File

@ -1,163 +0,0 @@
package awais.instagrabber.customviews.emoji;
import android.content.Context;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;
import awais.instagrabber.R;
import awais.instagrabber.customviews.emoji.EmojiPicker.OnBackspaceClickListener;
import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
import awais.instagrabber.utils.Utils;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
/**
* https://stackoverflow.com/a/33897583/1436766
*/
public class EmojiPopupWindow extends PopupWindow {
private int keyBoardHeight = 0;
private Boolean pendingOpen = false;
private Boolean isOpened = false;
private final View rootView;
private final Context context;
private final OnEmojiClickListener onEmojiClickListener;
private final OnBackspaceClickListener onBackspaceClickListener;
private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener;
/**
* Constructor
*
* @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height.
*/
public EmojiPopupWindow(final View rootView,
final OnEmojiClickListener onEmojiClickListener,
final OnBackspaceClickListener onBackspaceClickListener) {
super(rootView.getContext());
this.rootView = rootView;
this.context = rootView.getContext();
this.onEmojiClickListener = onEmojiClickListener;
this.onBackspaceClickListener = onBackspaceClickListener;
View customView = createCustomView();
setContentView(customView);
setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
//default size
setSize((int) context.getResources().getDimension(R.dimen.keyboard_height), MATCH_PARENT);
}
/**
* Set the listener for the event of keyboard opening or closing.
*/
public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) {
this.onSoftKeyboardOpenCloseListener = listener;
}
/**
* Use this function to show the emoji popup.
* NOTE: Since, the soft keyboard sizes are variable on different android devices, the
* library needs you to open the soft keyboard atleast once before calling this function.
* If that is not possible see showAtBottomPending() function.
*/
public void showAtBottom() {
showAtLocation(rootView, Gravity.BOTTOM, 0, 0);
}
/**
* Use this function when the soft keyboard has not been opened yet. This
* will show the emoji popup after the keyboard is up next time.
* Generally, you will be calling InputMethodManager.showSoftInput function after
* calling this function.
*/
public void showAtBottomPending() {
if (isKeyBoardOpen())
showAtBottom();
else
pendingOpen = true;
}
/**
* @return Returns true if the soft keyboard is open, false otherwise.
*/
public Boolean isKeyBoardOpen() {
return isOpened;
}
/**
* Dismiss the popup
*/
@Override
public void dismiss() {
super.dismiss();
}
/**
* Call this function to resize the emoji popup according to your soft keyboard size
*/
public void setSizeForSoftKeyboard() {
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = getUsableScreenHeight();
int heightDifference = screenHeight - (r.bottom - r.top);
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
heightDifference -= context.getResources()
.getDimensionPixelSize(resourceId);
}
if (heightDifference > 100) {
keyBoardHeight = heightDifference;
setSize(MATCH_PARENT, keyBoardHeight);
if (!isOpened) {
if (onSoftKeyboardOpenCloseListener != null)
onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
}
isOpened = true;
if (pendingOpen) {
showAtBottom();
pendingOpen = false;
}
} else {
isOpened = false;
if (onSoftKeyboardOpenCloseListener != null)
onSoftKeyboardOpenCloseListener.onKeyboardClose();
}
});
}
private int getUsableScreenHeight() {
return Utils.displayMetrics.heightPixels;
}
/**
* Manually set the popup window size
*
* @param width Width of the popup
* @param height Height of the popup
*/
public void setSize(int width, int height) {
setWidth(width);
setHeight(height);
}
private View createCustomView() {
final EmojiPicker emojiPicker = new EmojiPicker(context);
final LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
emojiPicker.setLayoutParams(layoutParams);
emojiPicker.init(rootView, onEmojiClickListener, onBackspaceClickListener);
return emojiPicker;
}
public interface OnSoftKeyboardOpenCloseListener {
void onKeyboardOpen(int keyBoardHeight);
void onKeyboardClose();
}
}

View File

@ -25,7 +25,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Spanned;
import android.text.TextPaint;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.emoji.text.EmojiCompat;

View File

@ -0,0 +1,320 @@
package awais.instagrabber.customviews.helpers;
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.transition.Transition;
import androidx.transition.TransitionListenerAdapter;
import androidx.transition.TransitionValues;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.BuildConfig;
/**
* This transition tracks changes to the text in TextView targets. If the text
* changes between the start and end scenes, the transition ensures that the
* starting text stays until the transition ends, at which point it changes
* to the end text. This is useful in situations where you want to resize a
* text view to its new size before displaying the text that goes there.
*/
public class ChangeText extends Transition {
private static final String LOG_TAG = "TextChange";
private static final String PROPNAME_TEXT = "android:textchange:text";
private static final String PROPNAME_TEXT_SELECTION_START =
"android:textchange:textSelectionStart";
private static final String PROPNAME_TEXT_SELECTION_END =
"android:textchange:textSelectionEnd";
private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor";
private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP;
private boolean crossFade;
/**
* Flag specifying that the text in affected/changing TextView targets will keep
* their original text during the transition, setting it to the final text when
* the transition ends. This is the default behavior.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_KEEP = 0;
/**
* Flag specifying that the text changing animation should first fade
* out the original text completely. The new text is set on the target
* view at the end of the fade-out animation. This transition is typically
* used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more
* flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other
* transitions to be run sequentially or in parallel with these fades.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_OUT = 1;
/**
* Flag specifying that the text changing animation should fade in the
* end text into the affected target view(s). This transition is typically
* used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT}
* transition, possibly with other transitions running as well, such as
* a sequence to fade out, then resize the view, then fade in.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_IN = 2;
/**
* Flag specifying that the text changing animation should first fade
* out the original text completely and then fade in the
* new text.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_OUT_IN = 3;
private static final String[] sTransitionProperties = {
PROPNAME_TEXT,
PROPNAME_TEXT_SELECTION_START,
PROPNAME_TEXT_SELECTION_END
};
/**
* Sets the type of changing animation that will be run, one of
* {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT},
* {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}.
*
* @param changeBehavior The type of fading animation to use when this
* transition is run.
* @return this textChange object.
*/
public ChangeText setChangeBehavior(int changeBehavior) {
if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) {
mChangeBehavior = changeBehavior;
}
return this;
}
public ChangeText setCrossFade(final boolean crossFade) {
this.crossFade = crossFade;
return this;
}
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
}
/**
* Returns the type of changing animation that will be run.
*
* @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT},
* {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}.
*/
public int getChangeBehavior() {
return mChangeBehavior;
}
private void captureValues(TransitionValues transitionValues) {
if (transitionValues.view instanceof TextView) {
TextView textview = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT, textview.getText());
if (textview instanceof EditText) {
transitionValues.values.put(PROPNAME_TEXT_SELECTION_START,
textview.getSelectionStart());
transitionValues.values.put(PROPNAME_TEXT_SELECTION_END,
textview.getSelectionEnd());
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor());
}
}
}
@Override
public void captureStartValues(@NonNull TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(@NonNull TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null ||
!(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) {
return null;
}
final TextView view = (TextView) endValues.view;
Map<String, Object> startVals = startValues.values;
Map<String, Object> endVals = endValues.values;
final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ?
(CharSequence) startVals.get(PROPNAME_TEXT) : "";
final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ?
(CharSequence) endVals.get(PROPNAME_TEXT) : "";
final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd;
if (view instanceof EditText) {
startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ?
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1;
startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ?
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart;
endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ?
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1;
endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ?
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart;
} else {
startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1;
}
if (!Objects.equals(startText, endText)) {
final int startColor;
final int endColor;
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(startText);
if (view instanceof EditText) {
setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
}
}
Animator anim;
if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) {
startColor = endColor = 0;
anim = ValueAnimator.ofFloat(0, 1);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (Objects.equals(startText, view.getText())) {
// Only set if it hasn't been changed since anim started
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
}
}
}
});
} else {
startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
// Fade out start text
ValueAnimator outAnim = null, inAnim = null;
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
mChangeBehavior == CHANGE_BEHAVIOR_OUT) {
outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0);
outAnim.addUpdateListener(animation -> {
int currAlpha = (Integer) animation.getAnimatedValue();
view.setTextColor(currAlpha << 24 | startColor & 0xffffff);
});
outAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (Objects.equals(startText, view.getText())) {
// Only set if it hasn't been changed since anim started
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart,
endSelectionEnd);
}
}
// restore opaque alpha and correct end color
view.setTextColor(endColor);
}
});
}
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
mChangeBehavior == CHANGE_BEHAVIOR_IN) {
inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor));
inAnim.addUpdateListener(animation -> {
int currAlpha = (Integer) animation.getAnimatedValue();
view.setTextColor(currAlpha << 24 | endColor & 0xffffff);
});
inAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// restore opaque alpha and correct end color
view.setTextColor(endColor);
}
});
}
if (outAnim != null && inAnim != null) {
anim = new AnimatorSet();
final AnimatorSet animatorSet = (AnimatorSet) anim;
if (crossFade) {
animatorSet.playTogether(outAnim, inAnim);
} else {
animatorSet.playSequentially(outAnim, inAnim);
}
} else if (outAnim != null) {
anim = outAnim;
} else {
// Must be an in-only animation
anim = inAnim;
}
}
TransitionListener transitionListener = new TransitionListenerAdapter() {
int mPausedColor = 0;
@Override
public void onTransitionPause(@NonNull Transition transition) {
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
}
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
mPausedColor = view.getCurrentTextColor();
view.setTextColor(endColor);
}
}
@Override
public void onTransitionResume(@NonNull Transition transition) {
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(startText);
if (view instanceof EditText) {
setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
}
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
view.setTextColor(mPausedColor);
}
}
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
}
};
addListener(transitionListener);
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "createAnimator returning " + anim);
}
return anim;
}
return null;
}
private void setSelection(EditText editText, int start, int end) {
if (start >= 0 && end >= 0) {
editText.setSelection(start, end);
}
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.customviews.helpers;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
@ -12,6 +14,13 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScrollBehavior<BottomNavigationView> {
private static final String TAG = "CustomHideBottomView";
public CustomHideBottomViewOnScrollBehavior() {
}
public CustomHideBottomViewOnScrollBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final BottomNavigationView child,
@ -23,7 +32,13 @@ public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScroll
}
@Override
public void onNestedPreScroll(@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final BottomNavigationView child, @NonNull final View target, final int dx, final int dy, @NonNull final int[] consumed, final int type) {
public void onNestedPreScroll(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final BottomNavigationView child,
@NonNull final View target,
final int dx,
final int dy,
@NonNull final int[] consumed,
final int type) {
if (dy > 0) {
slideDown(child);
} else if (dy < 0) {

View File

@ -7,17 +7,24 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private final int spacing;
private final int halfSpace;
private boolean hasHeader;
public GridSpacingItemDecoration(int spacing) {
this.spacing = spacing;
halfSpace = spacing / 2;
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final int halfSpace = spacing / 2;
if (hasHeader && parent.getChildAdapterPosition(view) == 0) {
outRect.bottom = halfSpace;
outRect.left = -halfSpace;
outRect.right = -halfSpace;
return;
}
if (parent.getPaddingLeft() != halfSpace) {
parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
parent.setPadding(halfSpace, hasHeader ? 0 : halfSpace, halfSpace, halfSpace);
parent.setClipToPadding(false);
}
outRect.top = halfSpace;
@ -25,4 +32,8 @@ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
outRect.left = halfSpace;
outRect.right = halfSpace;
}
public void setHasHeader(final boolean hasHeader) {
this.hasHeader = hasHeader;
}
}

View File

@ -22,14 +22,16 @@ import java.util.List;
import awais.instagrabber.db.dao.AccountDao;
import awais.instagrabber.db.dao.DMLastNotifiedDao;
import awais.instagrabber.db.dao.FavoriteDao;
import awais.instagrabber.db.dao.RecentSearchDao;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.entities.RecentSearch;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Utils;
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class},
version = 5)
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class, RecentSearch.class},
version = 6)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = AppDatabase.class.getSimpleName();
@ -42,12 +44,14 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract DMLastNotifiedDao dmLastNotifiedDao();
public abstract RecentSearchDao recentSearchDao();
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
.build();
}
}
@ -156,6 +160,21 @@ public abstract class AppDatabase extends RoomDatabase {
}
};
static final Migration MIGRATION_5_6 = new Migration(5, 6) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `recent_searches` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`ig_id` TEXT NOT NULL, " +
"`name` TEXT NOT NULL, " +
"`username` TEXT, " +
"`pic_url` TEXT, " +
"`type` TEXT NOT NULL, " +
"`last_searched_on` INTEGER NOT NULL)");
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `recent_searches` (`ig_id`, `type`)");
}
};
@NonNull
private static List<Favorite> backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
// check if old favorites table had the column query_display

View File

@ -0,0 +1,37 @@
package awais.instagrabber.db.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
import awais.instagrabber.db.entities.RecentSearch;
import awais.instagrabber.models.enums.FavoriteType;
@Dao
public interface RecentSearchDao {
@Query("SELECT * FROM recent_searches ORDER BY last_searched_on DESC")
List<RecentSearch> getAllRecentSearches();
@Query("SELECT * FROM recent_searches WHERE `ig_id` = :igId AND `type` = :type")
RecentSearch getRecentSearchByIgIdAndType(String igId, FavoriteType type);
@Query("SELECT * FROM recent_searches WHERE instr(`name`, :query) > 0")
List<RecentSearch> findRecentSearchesWithNameContaining(String query);
@Insert
Long insertRecentSearch(RecentSearch recentSearch);
@Update
void updateRecentSearch(RecentSearch recentSearch);
@Delete
void deleteRecentSearch(RecentSearch recentSearch);
// @Query("DELETE from recent_searches")
// void deleteAllRecentSearches();
}

View File

@ -0,0 +1,57 @@
package awais.instagrabber.db.datasources;
import android.content.Context;
import androidx.annotation.NonNull;
import java.util.List;
import awais.instagrabber.db.AppDatabase;
import awais.instagrabber.db.dao.RecentSearchDao;
import awais.instagrabber.db.entities.RecentSearch;
import awais.instagrabber.models.enums.FavoriteType;
public class RecentSearchDataSource {
private static final String TAG = RecentSearchDataSource.class.getSimpleName();
private static RecentSearchDataSource INSTANCE;
private final RecentSearchDao recentSearchDao;
private RecentSearchDataSource(final RecentSearchDao recentSearchDao) {
this.recentSearchDao = recentSearchDao;
}
public static synchronized RecentSearchDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
synchronized (RecentSearchDataSource.class) {
if (INSTANCE == null) {
final AppDatabase database = AppDatabase.getDatabase(context);
INSTANCE = new RecentSearchDataSource(database.recentSearchDao());
}
}
}
return INSTANCE;
}
public RecentSearch getRecentSearchByIgIdAndType(@NonNull final String igId, @NonNull final FavoriteType type) {
return recentSearchDao.getRecentSearchByIgIdAndType(igId, type);
}
@NonNull
public final List<RecentSearch> getAllRecentSearches() {
return recentSearchDao.getAllRecentSearches();
}
public final void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch) {
if (recentSearch.getId() != 0) {
recentSearchDao.updateRecentSearch(recentSearch);
return;
}
recentSearchDao.insertRecentSearch(recentSearch);
}
public final void deleteRecentSearch(@NonNull final RecentSearch recentSearch) {
recentSearchDao.deleteRecentSearch(recentSearch);
}
}

View File

@ -0,0 +1,185 @@
package awais.instagrabber.db.entities;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.time.LocalDateTime;
import java.util.Objects;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.repositories.responses.search.SearchItem;
@Entity(tableName = RecentSearch.TABLE_NAME, indices = {@Index(value = {RecentSearch.COL_IG_ID, RecentSearch.COL_TYPE}, unique = true)})
public class RecentSearch {
private static final String TAG = RecentSearch.class.getSimpleName();
public static final String TABLE_NAME = "recent_searches";
private static final String COL_ID = "id";
public static final String COL_IG_ID = "ig_id";
private static final String COL_NAME = "name";
private static final String COL_USERNAME = "username";
private static final String COL_PIC_URL = "pic_url";
public static final String COL_TYPE = "type";
private static final String COL_LAST_SEARCHED_ON = "last_searched_on";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = COL_ID)
private final int id;
@ColumnInfo(name = COL_IG_ID)
@NonNull
private final String igId;
@ColumnInfo(name = COL_NAME)
@NonNull
private final String name;
@ColumnInfo(name = COL_USERNAME)
private final String username;
@ColumnInfo(name = COL_PIC_URL)
private final String picUrl;
@ColumnInfo(name = COL_TYPE)
@NonNull
private final FavoriteType type;
@ColumnInfo(name = COL_LAST_SEARCHED_ON)
@NonNull
private final LocalDateTime lastSearchedOn;
@Ignore
public RecentSearch(final String igId,
final String name,
final String username,
final String picUrl,
final FavoriteType type,
final LocalDateTime lastSearchedOn) {
this(0, igId, name, username, picUrl, type, lastSearchedOn);
}
public RecentSearch(final int id,
@NonNull final String igId,
@NonNull final String name,
final String username,
final String picUrl,
@NonNull final FavoriteType type,
@NonNull final LocalDateTime lastSearchedOn) {
this.id = id;
this.igId = igId;
this.name = name;
this.username = username;
this.picUrl = picUrl;
this.type = type;
this.lastSearchedOn = lastSearchedOn;
}
public int getId() {
return id;
}
@NonNull
public String getIgId() {
return igId;
}
@NonNull
public String getName() {
return name;
}
public String getUsername() {
return username;
}
public String getPicUrl() {
return picUrl;
}
@NonNull
public FavoriteType getType() {
return type;
}
@NonNull
public LocalDateTime getLastSearchedOn() {
return lastSearchedOn;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RecentSearch that = (RecentSearch) o;
return Objects.equals(igId, that.igId) &&
Objects.equals(name, that.name) &&
Objects.equals(username, that.username) &&
Objects.equals(picUrl, that.picUrl) &&
type == that.type &&
Objects.equals(lastSearchedOn, that.lastSearchedOn);
}
@Override
public int hashCode() {
return Objects.hash(igId, name, username, picUrl, type, lastSearchedOn);
}
@NonNull
@Override
public String toString() {
return "RecentSearch{" +
"id=" + id +
", igId='" + igId + '\'' +
", name='" + name + '\'' +
", username='" + username + '\'' +
", picUrl='" + picUrl + '\'' +
", type=" + type +
", lastSearchedOn=" + lastSearchedOn +
'}';
}
@Nullable
public static RecentSearch fromSearchItem(@NonNull final SearchItem searchItem) {
final FavoriteType type = searchItem.getType();
if (type == null) return null;
try {
final String igId;
final String name;
final String username;
final String picUrl;
switch (type) {
case USER:
igId = String.valueOf(searchItem.getUser().getPk());
name = searchItem.getUser().getFullName();
username = searchItem.getUser().getUsername();
picUrl = searchItem.getUser().getProfilePicUrl();
break;
case HASHTAG:
igId = searchItem.getHashtag().getId();
name = searchItem.getHashtag().getName();
username = null;
picUrl = null;
break;
case LOCATION:
igId = String.valueOf(searchItem.getPlace().getLocation().getPk());
name = searchItem.getPlace().getTitle();
username = null;
picUrl = null;
break;
default:
return null;
}
return new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now());
} catch (Exception e) {
Log.e(TAG, "fromSearchItem: ", e);
}
return null;
}
}

View File

@ -0,0 +1,124 @@
package awais.instagrabber.db.repositories;
import androidx.annotation.NonNull;
import java.time.LocalDateTime;
import java.util.List;
import awais.instagrabber.db.datasources.RecentSearchDataSource;
import awais.instagrabber.db.entities.RecentSearch;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.AppExecutors;
public class RecentSearchRepository {
private static final String TAG = RecentSearchRepository.class.getSimpleName();
private static RecentSearchRepository instance;
private final AppExecutors appExecutors;
private final RecentSearchDataSource recentSearchDataSource;
private RecentSearchRepository(final AppExecutors appExecutors, final RecentSearchDataSource recentSearchDataSource) {
this.appExecutors = appExecutors;
this.recentSearchDataSource = recentSearchDataSource;
}
public static RecentSearchRepository getInstance(final RecentSearchDataSource recentSearchDataSource) {
if (instance == null) {
instance = new RecentSearchRepository(AppExecutors.getInstance(), recentSearchDataSource);
}
return instance;
}
public void getRecentSearch(@NonNull final String igId,
@NonNull final FavoriteType type,
final RepositoryCallback<RecentSearch> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (recentSearch == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(recentSearch);
});
});
}
public void getAllRecentSearches(final RepositoryCallback<List<RecentSearch>> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final List<RecentSearch> recentSearches = recentSearchDataSource.getAllRecentSearches();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(recentSearches);
});
});
}
public void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch,
final RepositoryCallback<Void> callback) {
insertOrUpdateRecentSearch(recentSearch.getIgId(), recentSearch.getName(), recentSearch.getUsername(), recentSearch.getPicUrl(),
recentSearch.getType(), callback);
}
public void insertOrUpdateRecentSearch(@NonNull final String igId,
@NonNull final String name,
final String username,
final String picUrl,
@NonNull final FavoriteType type,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
recentSearch = recentSearch == null
? new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now())
: new RecentSearch(recentSearch.getId(), igId, name, username, picUrl, type, LocalDateTime.now());
recentSearchDataSource.insertOrUpdateRecentSearch(recentSearch);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
public void deleteRecentSearchByIgIdAndType(@NonNull final String igId,
@NonNull final FavoriteType type,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
if (recentSearch != null) {
recentSearchDataSource.deleteRecentSearch(recentSearch);
}
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (recentSearch == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(null);
});
});
}
public void deleteRecentSearch(@NonNull final RecentSearch recentSearch,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
recentSearchDataSource.deleteRecentSearch(recentSearch);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
}

View File

@ -12,7 +12,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.Collections;
@ -25,8 +24,10 @@ import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.ProcessPhoenix;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -55,9 +56,14 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
}
CookieUtils.setupCookies(model.getCookie());
settingsHelper.putString(Constants.COOKIE, model.getCookie());
final FragmentActivity activity = getActivity();
if (activity != null) activity.recreate();
dismiss();
// final FragmentActivity activity = getActivity();
// if (activity != null) activity.recreate();
// dismiss();
AppExecutors.getInstance().mainThread().execute(() -> {
final Context context = getContext();
if (context == null) return;
ProcessPhoenix.triggerRebirth(context);
}, 200);
};
private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> {

View File

@ -190,16 +190,15 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
final NavController navController = NavHostFragment.findNavController(CollectionPostsFragment.this);
final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
}
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@ -243,8 +242,10 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
final TransitionSet transitionSet = new TransitionSet();
final Context context = getContext();
if (context == null) return;
transitionSet.addTransition(new ChangeBounds())
.addTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move))
.addTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.move))
.setDuration(200);
setSharedElementEnterTransition(transitionSet);
postponeEnterTransition();
@ -280,7 +281,8 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.collection_posts_menu, menu);
// delaying to make toolbar resume animation smooth, otherwise lags
binding.getRoot().postDelayed(() -> inflater.inflate(R.menu.collection_posts_menu, menu), 500);
}
@Override
@ -288,62 +290,60 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
else if (item.getItemId() == R.id.delete) {
} else if (item.getItemId() == R.id.delete) {
final Context context = getContext();
if (context == null) return false;
new AlertDialog.Builder(context)
.setTitle(R.string.delete_collection)
.setMessage(R.string.delete_collection_note)
.setPositiveButton(R.string.confirm, (d, w) -> {
collectionService.deleteCollection(
savedCollection.getId(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
SavedCollectionsFragment.pleaseRefresh = true;
NavHostFragment.findNavController(CollectionPostsFragment.this).navigateUp();
}
.setPositiveButton(R.string.confirm, (d, w) -> collectionService.deleteCollection(
savedCollection.getId(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
SavedCollectionsFragment.pleaseRefresh = true;
NavHostFragment.findNavController(CollectionPostsFragment.this).navigateUp();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting collection", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
})
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting collection", t);
try {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
}))
.setNegativeButton(R.string.cancel, null)
.show();
}
else if (item.getItemId() == R.id.edit) {
} else if (item.getItemId() == R.id.edit) {
final Context context = getContext();
if (context == null) return false;
final EditText input = new EditText(context);
new AlertDialog.Builder(context)
.setTitle(R.string.edit_collection)
.setView(input)
.setPositiveButton(R.string.confirm, (d, w) -> {
collectionService.editCollectionName(
savedCollection.getId(),
input.getText().toString(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
binding.collapsingToolbarLayout.setTitle(input.getText().toString());
SavedCollectionsFragment.pleaseRefresh = true;
}
.setPositiveButton(R.string.confirm, (d, w) -> collectionService.editCollectionName(
savedCollection.getId(),
input.getText().toString(),
new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
binding.collapsingToolbarLayout.setTitle(input.getText().toString());
SavedCollectionsFragment.pleaseRefresh = true;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error editing collection", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
})
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error editing collection", t);
try {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
}
}))
.setNegativeButton(R.string.cancel, null)
.show();
}
@ -443,8 +443,8 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
private void setupCover() {
final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null
? savedCollection.getCoverMedia()
: savedCollection.getCoverMedias().get(0));
? savedCollection.getCoverMedia()
: savedCollection.getCoverMedias().get(0));
final DraweeController controller = Fresco
.newDraweeControllerBuilder()
.setOldController(binding.cover.getController())

View File

@ -1,487 +0,0 @@
package awais.instagrabber.fragments;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.asyncs.CommentsFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentCommentsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.CommentsViewModel;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import static android.content.Context.INPUT_METHOD_SERVICE;
public final class CommentsViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "CommentsViewerFragment";
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private CommentsAdapter commentsAdapter;
private FragmentCommentsBinding binding;
private LinearLayoutManager layoutManager;
private RecyclerLazyLoader lazyLoader;
private String shortCode;
private long authorUserId, userIdFromCookie;
private String endCursor = null;
private Resources resources;
private InputMethodManager imm;
private AppCompatActivity fragmentActivity;
private LinearLayoutCompat root;
private boolean shouldRefresh = true, hasNextPage = false;
private MediaService mediaService;
private String postId;
private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning;
private CommentsViewModel commentsViewModel;
private final FetchListener<List<CommentModel>> fetchListener = new FetchListener<List<CommentModel>>() {
@Override
public void doBefore() {
binding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final List<CommentModel> commentModels) {
if (commentModels != null && commentModels.size() > 0) {
endCursor = commentModels.get(0).getEndCursor();
hasNextPage = commentModels.get(0).hasNextPage();
List<CommentModel> list = commentsViewModel.getList().getValue();
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
// final int oldSize = list != null ? list.size() : 0;
list.addAll(commentModels);
commentsViewModel.getList().postValue(list);
}
binding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor(null);
}
@Override
public void onFailure(Throwable t) {
stopCurrentExecutor(t);
}
};
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {
@Override
public void onClick(final CommentModel comment) {
onCommentClick(comment);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(CommentsViewerFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
openProfile(mention);
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailAddress) {
Utils.openEmailAddress(getContext(), emailAddress);
}
};
private final View.OnClickListener newCommentListener = v -> {
final Editable text = binding.commentText.getText();
final Context context = getContext();
if (context == null) return;
if (text == null || TextUtils.isEmpty(text.toString())) {
Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
if (userIdFromCookie == 0) return;
String replyToId = null;
final CommentModel commentModel = commentsAdapter.getSelected();
if (commentModel != null) {
replyToId = commentModel.getId();
}
mediaService.comment(postId, text.toString(), replyToId, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
commentsAdapter.clearSelection();
binding.commentText.setText("");
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error during comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity();
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
mediaService = MediaService.getInstance(deviceUuid, csrfToken, userIdFromCookie);
// setHasOptionsMenu(true);
}
@NonNull
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentCommentsBinding.inflate(getLayoutInflater());
binding.swipeRefreshLayout.setEnabled(false);
binding.swipeRefreshLayout.setNestedScrollingEnabled(false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
init();
shouldRefresh = false;
}
// @Override
// public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
// inflater.inflate(R.menu.follow, menu);
// menu.findItem(R.id.action_compare).setVisible(false);
// final MenuItem menuSearch = menu.findItem(R.id.action_search);
// final SearchView searchView = (SearchView) menuSearch.getActionView();
// searchView.setQueryHint(getResources().getString(R.string.action_search));
// searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
// @Override
// public boolean onQueryTextSubmit(final String query) {
// return false;
// }
//
// @Override
// public boolean onQueryTextChange(final String query) {
// // if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
// return true;
// }
// });
// }
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
commentsViewModel.getList().postValue(Collections.emptyList());
stopCurrentExecutor(null);
currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void init() {
if (getArguments() == null) return;
final CommentsViewerFragmentArgs fragmentArgs = CommentsViewerFragmentArgs.fromBundle(getArguments());
shortCode = fragmentArgs.getShortCode();
postId = fragmentArgs.getPostId();
authorUserId = fragmentArgs.getPostUserId();
// setTitle();
binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.swipeRefreshLayout.setRefreshing(true);
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
layoutManager = new LinearLayoutManager(getContext());
binding.rvComments.setLayoutManager(layoutManager);
commentsAdapter = new CommentsAdapter(commentCallback);
binding.rvComments.setAdapter(commentsAdapter);
commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList);
resources = getResources();
if (!TextUtils.isEmpty(cookie)) {
binding.commentField.setStartIconVisible(false);
binding.commentField.setEndIconVisible(false);
binding.commentField.setVisibility(View.VISIBLE);
binding.commentText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
binding.commentField.setStartIconVisible(s.length() > 0);
binding.commentField.setEndIconVisible(s.length() > 0);
}
@Override
public void afterTextChanged(final Editable s) {}
});
binding.commentField.setStartIconOnClickListener(v -> {
commentsAdapter.clearSelection();
binding.commentText.setText("");
});
binding.commentField.setEndIconOnClickListener(newCommentListener);
}
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (hasNextPage && !TextUtils.isEmpty(endCursor))
currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
binding.rvComments.addOnScrollListener(lazyLoader);
stopCurrentExecutor(null);
onRefresh();
}
// private void setTitle() {
// final ActionBar actionBar = fragmentActivity.getSupportActionBar();
// if (actionBar == null) return;
// actionBar.setTitle(R.string.title_comments);
// actionBar.setSubtitle(shortCode);
// }
private void onCommentClick(final CommentModel commentModel) {
final String username = commentModel.getProfileModel().getUsername();
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText());
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
String[] commentDialogList;
if (!TextUtils.isEmpty(cookie)
&& userIdFromCookie != 0
&& (userIdFromCookie == commentModel.getProfileModel().getPk() || userIdFromCookie == authorUserId)) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_translate_comment),
resources.getString(R.string.comment_viewer_delete_comment)
};
} else if (!TextUtils.isEmpty(cookie)) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_translate_comment)
};
} else {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers)
};
}
final Context context = getContext();
if (context == null) return;
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
final User profileModel = commentModel.getProfileModel();
switch (which) {
case 0: // open profile
openProfile("@" + profileModel.getUsername());
break;
case 1: // copy comment
Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText());
break;
case 2: // see comment likers, this is surprisingly available to anons
final NavController navController = getNavController();
if (navController != null) {
final Bundle bundle = new Bundle();
bundle.putString("postId", commentModel.getId());
bundle.putBoolean("isComment", true);
navController.navigate(R.id.action_global_likesViewerFragment, bundle);
} else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
break;
case 3: // reply to comment
commentsAdapter.setSelected(commentModel);
String mention = "@" + profileModel.getUsername() + " ";
binding.commentText.setText(mention);
binding.commentText.requestFocus();
binding.commentText.setSelection(mention.length());
binding.commentText.postDelayed(() -> {
imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.commentText, 0);
}, 200);
break;
case 4: // like/unlike comment
if (!commentModel.getLiked()) {
mediaService.commentLike(commentModel.getId(), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
commentsAdapter.setLiked(commentModel, true);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error liking comment", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
return;
}
mediaService.commentUnlike(commentModel.getId(), new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
commentsAdapter.setLiked(commentModel, false);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
break;
case 5: // translate comment
mediaService.translate(commentModel.getId(), "2", new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
if (TextUtils.isEmpty(result)) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
new AlertDialog.Builder(context)
.setTitle(username)
.setMessage(result)
.setPositiveButton(R.string.ok, null)
.show();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error translating comment", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
break;
case 6: // delete comment
if (userIdFromCookie == 0) return;
mediaService.deleteComment(
postId, commentModel.getId(),
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting comment", t);
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
}
});
break;
}
};
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
}
private void openProfile(final String username) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action);
}
private void stopCurrentExecutor(final Throwable t) {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
if (t != null) {
try {
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setRefreshing(false);
}
catch(Throwable e) {}
}
}
@Nullable
private NavController getNavController() {
NavController navController = null;
try {
navController = NavHostFragment.findNavController(this);
} catch (IllegalStateException e) {
Log.e(TAG, "navigateToProfile", e);
}
return navController;
}
}

View File

@ -41,7 +41,9 @@ public class FavoritesFragment extends Fragment {
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
final Context context = getContext();
if (context == null) return;
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
}
@NonNull

View File

@ -1,5 +1,6 @@
package awais.instagrabber.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
@ -23,12 +24,14 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
@ -60,6 +63,7 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
@ -74,9 +78,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@ -86,7 +87,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private MainActivity fragmentActivity;
private FragmentHashtagBinding binding;
private CoordinatorLayout root;
private MotionLayout root;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private boolean opening = false;
@ -213,7 +214,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final View mainPostImage,
final int position) {
if (opening) return;
if (TextUtils.isEmpty(feedModel.getUser().getUsername())) {
final User user = feedModel.getUser();
if (user == null) return;
if (TextUtils.isEmpty(user.getUsername())) {
opening = true;
new PostFetcher(feedModel.getCode(), newFeedModel -> {
opening = false;
@ -223,15 +226,15 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
return;
}
opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
final NavController navController = NavHostFragment.findNavController(HashTagFragment.this);
final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
}
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
opening = false;
}
};
@ -301,13 +304,11 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
fragmentActivity.setCollapsingView(hashtagDetailsBinding.getRoot());
return root;
}
binding = FragmentHashtagBinding.inflate(inflater, container, false);
root = binding.getRoot();
hashtagDetailsBinding = LayoutHashtagDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false);
fragmentActivity.setCollapsingView(hashtagDetailsBinding.getRoot());
hashtagDetailsBinding = binding.header;
return root;
}
@ -364,14 +365,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (hashtagDetailsBinding != null) {
fragmentActivity.removeCollapsingView(hashtagDetailsBinding.getRoot());
}
}
private void init() {
if (getArguments() == null) return;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
@ -396,6 +389,17 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
}
private void setHashtagDetails() {
@ -403,8 +407,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
try {
Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setEnabled(false);
}
catch (Exception ignored) {}
} catch (Exception ignored) {}
return;
}
setTitle();
@ -423,47 +426,49 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
final long userId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
if (csrfToken != null && userId != 0 && deviceUuid != null) {
if (csrfToken != null && userId != 0) {
hashtagDetailsBinding.btnFollowTag.setClickable(false);
tagsService.changeFollow(hashtagModel.getFollowing() == FollowingType.FOLLOWING ? "unfollow" : "follow",
hashtag,
csrfToken,
userId,
deviceUuid,
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24);
}
tagsService.changeFollow(
hashtagModel.getFollowing() == FollowingType.FOLLOWING ? "unfollow" : "follow",
hashtag,
csrfToken,
userId,
deviceUuid,
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24);
}
@Override
public void onFailure(@NonNull final Throwable t) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(root,
message != null ? message
: getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
return;
@Override
public void onFailure(@NonNull final Throwable t) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(
root,
message != null ? message : getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
}
});
} else {
hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE);
}
hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
final Context context = getContext();
if (context == null) return;
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
favoriteRepository.getFavorite(hashtag, FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
@ -552,7 +557,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void showSnackbar(final String message) {
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
@SuppressLint("ShowToast") final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())

View File

@ -11,6 +11,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@ -34,7 +35,7 @@ import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class LikesViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LikesViewerFragment";
private static final String TAG = LikesViewerFragment.class.getSimpleName();
private FragmentLikesBinding binding;
private RecyclerLazyLoader lazyLoader;
@ -58,6 +59,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
});
binding.rvLikes.setAdapter(likesAdapter);
binding.rvLikes.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rvLikes.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
binding.swipeRefreshLayout.setRefreshing(false);
}
@ -71,7 +73,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
}
};
private final ServiceCallback<GraphQLUserListFetchResponse> acb = new ServiceCallback<GraphQLUserListFetchResponse>() {
private final ServiceCallback<GraphQLUserListFetchResponse> anonCb = new ServiceCallback<GraphQLUserListFetchResponse>() {
@Override
public void onSuccess(final GraphQLUserListFetchResponse result) {
endCursor = result.getNextMaxId();
@ -127,7 +129,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
public void onRefresh() {
if (isComment && !isLoggedIn) {
lazyLoader.resetState();
graphQLService.fetchCommentLikers(postId, null, acb);
graphQLService.fetchCommentLikers(postId, null, anonCb);
} else mediaService.fetchLikes(postId, isComment, cb);
}
@ -141,9 +143,10 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
if (isComment && !isLoggedIn) {
final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
binding.rvLikes.setLayoutManager(layoutManager);
binding.rvLikes.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.HORIZONTAL));
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!TextUtils.isEmpty(endCursor))
graphQLService.fetchCommentLikers(postId, endCursor, acb);
graphQLService.fetchCommentLikers(postId, endCursor, anonCb);
endCursor = null;
});
binding.rvLikes.addOnScrollListener(lazyLoader);

View File

@ -1,5 +1,6 @@
package awais.instagrabber.fragments;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@ -21,12 +22,14 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
@ -56,6 +59,7 @@ import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
@ -70,9 +74,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@ -80,7 +81,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private MainActivity fragmentActivity;
private FragmentLocationBinding binding;
private CoordinatorLayout root;
private MotionLayout root;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private boolean opening = false;
@ -204,7 +205,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final View mainPostImage,
final int position) {
if (opening) return;
if (TextUtils.isEmpty(feedModel.getUser().getUsername())) {
final User user = feedModel.getUser();
if (user == null) return;
if (TextUtils.isEmpty(user.getUsername())) {
opening = true;
new PostFetcher(feedModel.getCode(), newFeedModel -> {
opening = false;
@ -214,16 +217,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
return;
}
opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
final NavController navController = NavHostFragment.findNavController(LocationFragment.this);
final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
}
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
opening = false;
}
};
@ -295,13 +297,11 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
fragmentActivity.setCollapsingView(locationDetailsBinding.getRoot());
return root;
}
binding = FragmentLocationBinding.inflate(inflater, container, false);
root = binding.getRoot();
locationDetailsBinding = LayoutLocationDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false);
fragmentActivity.setCollapsingView(locationDetailsBinding.getRoot());
locationDetailsBinding = binding.header;
return root;
}
@ -358,14 +358,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (locationDetailsBinding != null) {
fragmentActivity.removeCollapsingView(locationDetailsBinding.getRoot());
}
}
private void init() {
if (getArguments() == null) return;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
@ -386,6 +378,17 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
.setSelectionModeCallback(selectionModeCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
}
private void fetchLocationModel() {
@ -399,8 +402,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
try {
Toast.makeText(getContext(), R.string.error_loading_location, Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setEnabled(false);
}
catch (Exception ignored) {}
} catch (Exception ignored) {}
return;
}
setTitle();
@ -409,16 +411,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final long locationId = locationModel.getPk();
// binding.swipeRefreshLayout.setRefreshing(true);
locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location);
// final String postCount = String.valueOf(locationModel.getCount());
// final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
// locationModel.getPostCount() > 2000000000L
// ? 2000000000
// : locationModel.getPostCount().intValue(),
// postCount));
// span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
// span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
// locationDetailsBinding.mainLocPostCount.setText(span);
// locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE);
// final String postCount = String.valueOf(locationModel.getCount());
// final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
// locationModel.getPostCount() > 2000000000L
// ? 2000000000
// : locationModel.getPostCount().intValue(),
// postCount));
// span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
// span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
// locationDetailsBinding.mainLocPostCount.setText(span);
// locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE);
locationDetailsBinding.locationFullName.setText(locationModel.getName());
CharSequence biography = locationModel.getAddress() + "\n" + locationModel.getCity();
// binding.locationBiography.setCaptionIsExpandable(true);
@ -431,22 +433,22 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} else {
locationDetailsBinding.locationBiography.setVisibility(View.VISIBLE);
locationDetailsBinding.locationBiography.setText(biography);
// locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> {
// final NavController navController = NavHostFragment.findNavController(this);
// final Bundle bundle = new Bundle();
// final String originalText = autoLinkItem.getOriginalText().trim();
// bundle.putString(ARG_HASHTAG, originalText);
// navController.navigate(R.id.action_global_hashTagFragment, bundle);
// });
// locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> {
// final String originalText = autoLinkItem.getOriginalText().trim();
// navigateToProfile(originalText);
// });
// locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context,
// autoLinkItem.getOriginalText()
// .trim()));
// locationDetailsBinding.locationBiography
// .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim()));
// locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> {
// final NavController navController = NavHostFragment.findNavController(this);
// final Bundle bundle = new Bundle();
// final String originalText = autoLinkItem.getOriginalText().trim();
// bundle.putString(ARG_HASHTAG, originalText);
// navController.navigate(R.id.action_global_hashTagFragment, bundle);
// });
// locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> {
// final String originalText = autoLinkItem.getOriginalText().trim();
// navigateToProfile(originalText);
// });
// locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context,
// autoLinkItem.getOriginalText()
// .trim()));
// locationDetailsBinding.locationBiography
// .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim()));
locationDetailsBinding.locationBiography.setOnLongClickListener(v -> {
Utils.copyText(context, biography);
return true;
@ -456,9 +458,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
if (!locationModel.getGeo().startsWith("geo:0.0,0.0?z=17")) {
locationDetailsBinding.btnMap.setVisibility(View.VISIBLE);
locationDetailsBinding.btnMap.setOnClickListener(v -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(locationModel.getGeo()));
startActivity(intent);
try {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(locationModel.getGeo()));
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.no_external_map_app, Toast.LENGTH_LONG).show();
Log.e(TAG, "setupLocationDetails: ", e);
} catch (Exception e) {
Log.e(TAG, "setupLocationDetails: ", e);
}
});
} else {
locationDetailsBinding.btnMap.setVisibility(View.GONE);

Some files were not shown because too many files have changed in this diff Show More