1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-16 11:47:29 +00:00

Merge pull request #170 from ammargitham/task/add-favourites

add favorites and bugfixes
This commit is contained in:
Austin Huang 2020-09-25 13:13:34 -04:00 committed by GitHub
commit 2f0e81d86f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 3684 additions and 1846 deletions

View File

@ -4,6 +4,7 @@
], ],
"imageSize": 100, "imageSize": 100,
"commit": false, "commit": false,
"badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg)](#contributors)",
"contributors": [ "contributors": [
{ {
"login": "austinhuang0131", "login": "austinhuang0131",
@ -79,8 +80,7 @@
"blog", "blog",
"bug", "bug",
"ideas", "ideas",
"question", "question"
"userTesting"
] ]
}, },
{ {
@ -89,8 +89,18 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/53869451", "avatar_url": "https://avatars0.githubusercontent.com/u/53869451",
"profile": "https://airikr.me/", "profile": "https://airikr.me/",
"contributions": [ "contributions": [
"question", "ideas",
"ideas" "question"
]
},
{
"login": "Akrai",
"name": "Akrai",
"avatar_url": "https://avatars1.githubusercontent.com/u/5624597?v=4",
"profile": "https://github.com/Akrai",
"contributions": [
"ideas",
"translation"
] ]
}, },
{ {
@ -111,6 +121,15 @@
"translation" "translation"
] ]
}, },
{
"login": "faydin",
"name": "Fatih Aydın",
"avatar_url": "https://avatars2.githubusercontent.com/u/22706676?v=4",
"profile": "https://github.com/faydin",
"contributions": [
"translation"
]
},
{ {
"login": "kernoeb", "login": "kernoeb",
"name": "kernoeb", "name": "kernoeb",
@ -167,10 +186,17 @@
} }
], ],
"contributorsPerLine": 6, "contributorsPerLine": 6,
"projectName": "instagrabber", "projectName": "barinsta",
"projectOwner": "austinhuang0131", "projectOwner": "austinhuang0131",
"repoType": "github", "repoType": "github",
"repoHost": "https://github.com", "repoHost": "https://github.com",
"skipCi": true, "skipCi": true,
"types": {
"translation": {
"symbol": "🌍",
"description": "Translation",
"link": "https://crowdin.com/project/instagrabber"
}
},
"commitConvention": "none" "commitConvention": "none"
} }

4
.gitignore vendored
View File

@ -9,11 +9,11 @@
/.idea/workspace.xml /.idea/workspace.xml
/.idea/navEditor.xml /.idea/navEditor.xml
/.idea/assetWizardSettings.xml /.idea/assetWizardSettings.xml
/.idea/git_toolbox_prj.xml
/.idea/dbnavigator.xml
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
app/release app/release
.idea/git_toolbox_prj.xml

View File

@ -6,7 +6,9 @@
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/) [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) [![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) [![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/) [![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-18-orange.svg)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
We're previously known as InstaGrabber and still in process of rebranding. For documentation, visit [InstaGrabber.AustinHuang.me](https://instagrabber.austinhuang.me). We're previously known as InstaGrabber and still in process of rebranding. For documentation, visit [InstaGrabber.AustinHuang.me](https://instagrabber.austinhuang.me).
@ -34,37 +36,35 @@ Version status: ![F-Droid](https://img.shields.io/f-droid/v/me.austinhuang.insta
### Contributors ### Contributors
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> Prominent contributors are listed here in the [all-contributors](https://allcontributors.org/) specifications, see [emoji key](https://allcontributors.org/docs/en/emoji-key). [Want to contribute to Barinsta?](https://github.com/austinhuang0131/barinsta/blob/master/.github/CONTRIBUTING.md)
[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Prominent contributors are listed here in the [all-contributors](https://allcontributors.org/) specifications. See [emoji key](https://allcontributors.org/docs/en/emoji-key).
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <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/instagrabber/commits?author=austinhuang0131" title="Code">💻</a> <a href="https://github.com/austinhuang0131/instagrabber/commits?author=austinhuang0131" title="Documentation">📖</a> <a href="#question-austinhuang0131" title="Answering Questions">💬</a> <a href="#translation-austinhuang0131" title="Translation">🌍</a> <a href="#ideas-austinhuang0131" title="Ideas, Planning, & Feedback">🤔</a></td> <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/instagrabber/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/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/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/instagrabber/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/instagrabber/issues?q=author%3Aandersonvom" title="Bug reports">🐛</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="http://rerolledgeek.blogspot.com/"><img src="https://avatars3.githubusercontent.com/u/5278488?s=100" width="100px;" alt=""/><br /><sub><b>AWAiS</b></sub></a><br /><a href="https://github.com/austinhuang0131/instagrabber/commits?author=AwaisKing" title="Code">💻</a> <a href="#ideas-AwaisKing" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="http://rerolledgeek.blogspot.com/"><img src="https://avatars3.githubusercontent.com/u/5278488?s=100" width="100px;" alt=""/><br /><sub><b>AWAiS</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=AwaisKing" title="Code">💻</a> <a href="#ideas-AwaisKing" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://stefannajdovski.com/"><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="#translation-snajdovski" title="Translation">🌍</a></td> <td align="center"><a href="https://stefannajdovski.com/"><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>
<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="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>
</tr> </tr>
<tr> <tr>
<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/instagrabber/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> <a href="#userTesting-Shadowspear123" title="User Testing">📓</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>
<td align="center"><a href="https://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#question-e-edgren" title="Answering Questions">💬</a> <a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="https://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-e-edgren" title="Answering Questions">💬</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="#translation-Galang23" 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/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="#translation-farzadx" 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>
<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="#translation-kernoeb" 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>
<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="#translation-Lego8486" title="Translation">🌍</a></td> <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>
</tr> </tr>
<tr> <tr>
<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="#translation-MoaufmKlo" title="Translation">🌍</a></td> <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/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="#translation-peterge1998" 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/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="#translation-RAMAR-RAR" 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/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="#translation-wagnim" title="Translation">🌍</a></td> <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/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/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>
</table> </table>
@ -99,7 +99,7 @@ Logo by [Stefan Najdovski](https://stefannajdovski.com/). Used under license.
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber) [![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber)
[![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber) [![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)
[![LGTM Grade](https://img.shields.io/lgtm/grade/java/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber) [![LGTM Grade](https://img.shields.io/lgtm/grade/java/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)
[![CodeFactor](https://www.codefactor.io/repository/github/austinhuang0131/instagrabber/badge)](https://www.codefactor.io/repository/github/austinhuang0131/instagrabber) [![CodeFactor](https://www.codefactor.io/repository/github/austinhuang0131/barinsta/badge)](https://www.codefactor.io/repository/github/austinhuang0131/barinsta)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e9cfcb7733f8477d92e5c0f30cac137a)](https://www.codacy.com/manual/austinhuang0131/instagrabber) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e9cfcb7733f8477d92e5c0f30cac137a)](https://www.codacy.com/manual/austinhuang0131/instagrabber)
[![Crowdin](https://badges.crowdin.net/instagrabber/localized.svg)](https://crowdin.com/project/instagrabber) [![Crowdin](https://badges.crowdin.net/instagrabber/localized.svg)](https://crowdin.com/project/instagrabber)

View File

@ -7,11 +7,11 @@ android {
defaultConfig { defaultConfig {
applicationId 'me.austinhuang.instagrabbr' applicationId 'me.austinhuang.instagrabbr'
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 50 versionCode 51
versionName '19.0-a1' versionName '19.0-a2'
multiDexEnabled true multiDexEnabled true
@ -41,7 +41,7 @@ dependencies {
def nav_version = "2.3.0" def nav_version = "2.3.0"
def preference_version = "1.1.1" def preference_version = "1.1.1"
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.exoplayer:exoplayer:2.11.1' implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
@ -54,6 +54,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.preference:preference:$preference_version" implementation "androidx.preference:preference:$preference_version"
// implementation 'com.github.hendrawd:StorageUtil:1.1.0'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0' implementation 'com.facebook.fresco:fresco:2.3.0'

View File

@ -21,7 +21,7 @@
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=".Main" android:taskAffinity=".Main"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View File

@ -1,11 +1,10 @@
package awais.instagrabber; package awais.instagrabber;
import android.app.Application;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import androidx.multidex.MultiDexApplication;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.core.ImagePipelineConfig;
@ -27,7 +26,7 @@ import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public final class InstaGrabberApplication extends MultiDexApplication { public final class InstaGrabberApplication extends Application {
private static final String TAG = "InstaGrabberApplication"; private static final String TAG = "InstaGrabberApplication";
@Override @Override

View File

@ -1,6 +1,5 @@
package awais.instagrabber.activities; package awais.instagrabber.activities;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -29,6 +28,7 @@ import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@ -38,6 +38,7 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -67,7 +68,7 @@ import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class MainActivity extends BaseLanguageActivity { public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener {
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
private static final List<Integer> SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList( private static final List<Integer> SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList(
@ -91,7 +92,9 @@ public class MainActivity extends BaseLanguageActivity {
R.id.followViewerFragment, R.id.followViewerFragment,
R.id.directMessagesSettingsFragment, R.id.directMessagesSettingsFragment,
R.id.notificationsViewer, R.id.notificationsViewer,
R.id.themePreferencesFragment); R.id.themePreferencesFragment,
R.id.favoritesFragment,
R.id.backupPreferencesFragment);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>(); private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment); private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
@ -106,6 +109,7 @@ public class MainActivity extends BaseLanguageActivity {
private Handler suggestionsFetchHandler; private Handler suggestionsFetchHandler;
private int firstFragmentGraphIndex; private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false; private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
private final ServiceConnection serviceConnection = new ServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override @Override
@ -152,6 +156,11 @@ public class MainActivity extends BaseLanguageActivity {
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) { if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
bindActivityCheckerService(); bindActivityCheckerService();
} }
getSupportFragmentManager().addOnBackStackChangedListener(this);
// Log.d("austin_debug", "dir: "+Arrays.toString(StorageUtil.getStorageDirectories(getApplicationContext())));
// final File sdcard = new File(StorageUtil.getStorageDirectories(getApplicationContext())[0]);
// Log.d("austin_debug", "files: "+Arrays.toString(sdcard.listFiles()));
} }
@Override @Override
@ -211,6 +220,21 @@ public class MainActivity extends BaseLanguageActivity {
unbindActivityCheckerService(); unbindActivityCheckerService();
} }
@Override
public void onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTaskRoot() && isBackStackEmpty) {
finishAfterTransition();
} else {
super.onBackPressed();
}
}
@Override
public void onBackStackChanged() {
final int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
isBackStackEmpty = backStackEntryCount == 0;
}
private void createNotificationChannels() { private void createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
@ -367,32 +391,26 @@ public class MainActivity extends BaseLanguageActivity {
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
if (!isLoggedIn) { if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids; main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
binding.bottomNavView.getMenu().clear(); binding.bottomNavView.getMenu().clear();
binding.bottomNavView.inflateMenu(R.menu.logged_out_bottom_navigation_menu); binding.bottomNavView.inflateMenu(R.menu.logged_out_bottom_navigation_menu);
if (selectedItemId == R.id.profile_nav_graph
|| selectedItemId == R.id.more_nav_graph) {
binding.bottomNavView.setSelectedItemId(selectedItemId);
} else {
setBottomNavSelectedItem(R.navigation.profile_nav_graph);
}
} }
final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); final List<Integer> mainNavList = getMainNavList(main_nav_ids);
final List<Integer> mainNavList = new ArrayList<>(navIds.length()); if (setDefaultFromSettings) {
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();
if (setDefaultFromSettings || !isLoggedIn) {
final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB); final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB);
try { try {
final int defaultNavId = TextUtils.isEmpty(defaultTabIdString) || !isLoggedIn final int defaultNavId = TextUtils.isEmpty(defaultTabIdString)
? R.navigation.profile_nav_graph ? R.navigation.profile_nav_graph
: Integer.parseInt(defaultTabIdString); : Integer.parseInt(defaultTabIdString);
final int index = mainNavList.indexOf(defaultNavId); final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) { if (index >= 0) firstFragmentGraphIndex = index;
firstFragmentGraphIndex = index; setBottomNavSelectedItem(defaultNavId);
final Integer menuId = NAV_TO_MENU_ID_MAP.get(defaultNavId);
if (menuId != null) {
binding.bottomNavView.setSelectedItemId(menuId);
}
}
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(TAG, "Error parsing id", e); Log.e(TAG, "Error parsing id", e);
} }
@ -408,6 +426,27 @@ public class MainActivity extends BaseLanguageActivity {
currentNavControllerLiveData = navControllerLiveData; currentNavControllerLiveData = navControllerLiveData;
} }
private void setBottomNavSelectedItem(final int navId) {
final Integer menuId = NAV_TO_MENU_ID_MAP.get(navId);
if (menuId != null) {
binding.bottomNavView.setSelectedItemId(menuId);
}
}
@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 NavController navController) { private void setupNavigation(final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController); NavigationUI.setupWithNavController(binding.toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> { navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
@ -582,4 +621,9 @@ public class MainActivity extends BaseLanguageActivity {
unbindService(serviceConnection); unbindService(serviceConnection);
isActivityCheckerServiceBound = false; isActivityCheckerServiceBound = false;
} }
@NonNull
public BottomNavigationView getBottomNavView() {
return binding.bottomNavView;
}
} }

View File

@ -0,0 +1,75 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemDirListBinding;
public final class DirectoryFilesAdapter extends ListAdapter<File, DirectoryFilesAdapter.ViewHolder> {
private final OnFileClickListener onFileClickListener;
private static final DiffUtil.ItemCallback<File> DIFF_CALLBACK = new DiffUtil.ItemCallback<File>() {
@Override
public boolean areItemsTheSame(@NonNull final File oldItem, @NonNull final File newItem) {
return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath());
}
@Override
public boolean areContentsTheSame(@NonNull final File oldItem, @NonNull final File newItem) {
return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath());
}
};
public DirectoryFilesAdapter(final OnFileClickListener onFileClickListener) {
super(DIFF_CALLBACK);
this.onFileClickListener = onFileClickListener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final ItemDirListBinding binding = ItemDirListBinding.inflate(inflater, parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
final File file = getItem(position);
holder.bind(file, onFileClickListener);
}
public interface OnFileClickListener {
void onFileClick(File file);
}
static final class ViewHolder extends RecyclerView.ViewHolder {
private final ItemDirListBinding binding;
private ViewHolder(final ItemDirListBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final File file, final OnFileClickListener onFileClickListener) {
if (file == null) return;
if (onFileClickListener != null) {
itemView.setOnClickListener(v -> onFileClickListener.onFileClick(file));
}
binding.text.setText(file.getName());
if (file.isDirectory()) {
binding.icon.setImageResource(R.drawable.ic_folder_24);
return;
}
binding.icon.setImageResource(R.drawable.ic_file_24);
}
}
}

View File

@ -0,0 +1,202 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
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 awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.DataBox;
public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final OnFavoriteClickListener clickListener;
private final OnFavoriteLongClickListener longClickListener;
private final AsyncListDiffer<FavoriteModelOrHeader> differ;
private static final DiffUtil.ItemCallback<FavoriteModelOrHeader> diffCallback = new DiffUtil.ItemCallback<FavoriteModelOrHeader>() {
@Override
public boolean areItemsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) {
boolean areSame = oldItem.isHeader() && newItem.isHeader();
if (!areSame) {
return false;
}
if (oldItem.isHeader()) {
return ObjectsCompat.equals(oldItem.header, newItem.header);
}
if (oldItem.model != null && newItem.model != null) {
return oldItem.model.getId() == newItem.model.getId();
}
return false;
}
@Override
public boolean areContentsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) {
boolean areSame = oldItem.isHeader() && newItem.isHeader();
if (!areSame) {
return false;
}
if (oldItem.isHeader()) {
return ObjectsCompat.equals(oldItem.header, newItem.header);
}
return ObjectsCompat.equals(oldItem.model, newItem.model);
}
};
public FavoritesAdapter(final OnFavoriteClickListener clickListener, final OnFavoriteLongClickListener longClickListener) {
this.clickListener = clickListener;
this.longClickListener = longClickListener;
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == 0) {
// header
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
}
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false);
return new FavoriteViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
if (getItemViewType(position) == 0) {
final FavoriteModelOrHeader modelOrHeader = getItem(position);
if (!modelOrHeader.isHeader()) return;
((FavSectionViewHolder) holder).bind(modelOrHeader.header);
return;
}
((FavoriteViewHolder) holder).bind(getItem(position).model, clickListener, longClickListener);
}
protected FavoriteModelOrHeader 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() ? 0 : 1;
}
public void submitList(@Nullable final List<DataBox.FavoriteModel> list) {
if (list == null) {
differ.submitList(null);
return;
}
differ.submitList(sectionAndSort(list));
}
public void submitList(@Nullable final List<DataBox.FavoriteModel> list, @Nullable final Runnable commitCallback) {
if (list == null) {
differ.submitList(null, commitCallback);
return;
}
differ.submitList(sectionAndSort(list), commitCallback);
}
@NonNull
private List<FavoriteModelOrHeader> sectionAndSort(@NonNull final List<DataBox.FavoriteModel> list) {
final List<DataBox.FavoriteModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
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;
return 0;
});
final List<FavoriteModelOrHeader> modelOrHeaders = new ArrayList<>();
for (int i = 0; i < listCopy.size(); i++) {
final DataBox.FavoriteModel model = listCopy.get(i);
final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1);
boolean prevWasSameType = prev != null && prev.model.getType() == model.getType();
if (prevWasSameType) {
// just add model
final FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader();
modelOrHeader.model = model;
modelOrHeaders.add(modelOrHeader);
continue;
}
// add header and model
FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader();
modelOrHeader.header = model.getType();
modelOrHeaders.add(modelOrHeader);
modelOrHeader = new FavoriteModelOrHeader();
modelOrHeader.model = model;
modelOrHeaders.add(modelOrHeader);
}
return modelOrHeaders;
}
private static class FavoriteModelOrHeader {
FavoriteType header;
DataBox.FavoriteModel model;
boolean isHeader() {
return header != null;
}
}
public interface OnFavoriteClickListener {
void onClick(final DataBox.FavoriteModel model);
}
public interface OnFavoriteLongClickListener {
boolean onLongClick(final DataBox.FavoriteModel model);
}
public static class FavSectionViewHolder extends RecyclerView.ViewHolder {
private final ItemFavSectionHeaderBinding binding;
public FavSectionViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final FavoriteType header) {
if (header == null) return;
final int headerText;
switch (header) {
case USER:
headerText = R.string.accounts;
break;
case HASHTAG:
headerText = R.string.hashtags;
break;
case LOCATION:
headerText = R.string.locations;
break;
default:
headerText = R.string.unknown;
break;
}
binding.getRoot().setText(headerText);
}
}
}

View File

@ -4,13 +4,19 @@ import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.ListAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.adapters.viewholder.NotificationViewHolder; import awais.instagrabber.adapters.viewholder.NotificationViewHolder;
import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.NotificationModel; import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
public final class NotificationsAdapter extends ListAdapter<NotificationModel, NotificationViewHolder> { public final class NotificationsAdapter extends ListAdapter<NotificationModel, NotificationViewHolder> {
private final OnNotificationClickListener notificationClickListener; private final OnNotificationClickListener notificationClickListener;
@ -49,6 +55,36 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
holder.bind(notificationModel, notificationClickListener); holder.bind(notificationModel, notificationClickListener);
} }
@Override
public void submitList(@Nullable final List<NotificationModel> list, @Nullable final Runnable commitCallback) {
if (list == null) {
super.submitList(null, commitCallback);
return;
}
super.submitList(sort(list), commitCallback);
}
@Override
public void submitList(@Nullable final List<NotificationModel> list) {
if (list == null) {
super.submitList(null);
return;
}
super.submitList(sort(list));
}
private List<NotificationModel> sort(final List<NotificationModel> list) {
final List<NotificationModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
if (o1.getType() == o2.getType()) return 0;
// keep requests at top
if (o1.getType() == NotificationType.REQUEST) return -1;
if (o2.getType() == NotificationType.REQUEST) return 1;
return 0;
});
return listCopy;
}
public interface OnNotificationClickListener { public interface OnNotificationClickListener {
void onNotificationClick(final NotificationModel model); void onNotificationClick(final NotificationModel model);
} }

View File

@ -1,75 +0,0 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.utils.DataBox;
public final class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> {
private List<T> items;
private final LayoutInflater layoutInflater;
private final View.OnClickListener onClickListener;
private final View.OnLongClickListener longClickListener;
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener) {
this(context, items, onClickListener, null);
}
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
this.layoutInflater = LayoutInflater.from(context);
this.items = items;
this.onClickListener = onClickListener;
this.longClickListener = longClickListener;
}
public void setItems(final List<T> items) {
this.items = items;
notifyDataSetChanged();
}
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
return new SimpleViewHolder(layoutInflater.
inflate(R.layout.item_dir_list, parent, false), onClickListener, longClickListener);
}
@Override
public void onBindViewHolder(@NonNull final SimpleViewHolder holder, final int position) {
final T item = items.get(position);
holder.itemView.setTag(item);
holder.text.setText(item.toString());
if (item instanceof DataBox.CookieModel && ((DataBox.CookieModel) item).isSelected() ||
item instanceof String && ((String) item).toLowerCase().endsWith(".zaai"))
holder.itemView.setBackgroundColor(0xF0_125687);
else
holder.itemView.setBackground(null);
}
@Override
public int getItemCount() {
return items != null ? items.size() : 0;
}
static final class SimpleViewHolder extends RecyclerView.ViewHolder {
private final TextView text;
private SimpleViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
super(itemView);
text = itemView.findViewById(android.R.id.text1);
itemView.setOnClickListener(onClickListener);
if (longClickListener != null) itemView.setOnLongClickListener(longClickListener);
}
}
}

View File

@ -73,7 +73,14 @@ public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHol
} }
} }
binding.tvUsername.setText(model.getThreadTitle()); binding.tvUsername.setText(model.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1]; final int length = itemModels.length;
DirectItemModel lastItemModel = null;
if (length != 0) {
lastItemModel = itemModels[length - 1];
}
if (lastItemModel == null) {
return;
}
final DirectItemType itemType = lastItemModel.getItemType(); final DirectItemType itemType = lastItemModel.getItemType();
// binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE); // binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = itemView.getContext(); final Context context = itemView.getContext();

View File

@ -0,0 +1,61 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.FavoritesAdapter;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "FavoriteViewHolder";
private final ItemSuggestionBinding binding;
public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.isVerified.setVisibility(View.GONE);
}
public void bind(final DataBox.FavoriteModel model,
final FavoritesAdapter.OnFavoriteClickListener clickListener,
final FavoritesAdapter.OnFavoriteLongClickListener longClickListener) {
// Log.d(TAG, "bind: " + model);
if (model == null) return;
itemView.setOnClickListener(v -> {
if (clickListener == null) return;
clickListener.onClick(model);
});
itemView.setOnLongClickListener(v -> {
if (clickListener == null) return false;
return longClickListener.onLongClick(model);
});
if (model.getType() == FavoriteType.HASHTAG) {
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
} else {
binding.ivProfilePic.setImageURI(model.getPicUrl());
}
binding.tvFullName.setText(model.getDisplayName());
binding.tvUsername.setVisibility(View.VISIBLE);
String query = model.getQuery();
switch (model.getType()) {
case HASHTAG:
query = "#" + query;
break;
case USER:
query = "@" + query;
break;
case LOCATION:
binding.tvUsername.setVisibility(View.GONE);
break;
default:
// do nothing
}
binding.tvUsername.setText(query);
}
}

View File

@ -212,9 +212,10 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
feedModelsList.trimToSize(); feedModelsList.trimToSize();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]); final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
if (feedModels[feedModels.length - 1] != null) final int length = feedModels.length;
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor); if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
}
result = feedModels; result = feedModels;
} }

View File

@ -8,6 +8,10 @@ import androidx.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
@ -21,6 +25,8 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> { public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
private static final String TAG = "HashtagFetcher";
private final FetchListener<HashtagModel> fetchListener; private final FetchListener<HashtagModel> fetchListener;
private final String hashtag; private final String hashtag;
@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
HashtagModel result = null; HashtagModel result = null;
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1").openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1")
.openConnection();
conn.setUseCaches(true); conn.setUseCaches(true);
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_HASHTAG); final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_HASHTAG);
final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media"); final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media");
if (timelineMedia.has("edges")) { if (timelineMedia.has("edges")) {
@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
user.getString("profile_pic_url"), user.getString("profile_pic_url"),
timelineMedia.getLong("count"), timelineMedia.getLong("count"),
user.optBoolean("is_following")); user.optBoolean("is_following"));
} else {
BufferedReader bufferedReader = null;
try {
final InputStream responseInputStream = conn.getErrorStream();
bufferedReader = new BufferedReader(new InputStreamReader(responseInputStream));
final StringBuilder builder = new StringBuilder();
for (String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
Log.d(TAG, "doInBackground: " + builder.toString());
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ignored) {
}
}
}
} }
conn.disconnect(); conn.disconnect();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return result; return result;

View File

@ -19,25 +19,23 @@ import awais.instagrabber.utils.NetworkUtils;
public final class HighlightsFetcher extends AsyncTask<Void, Void, List<HighlightModel>> { public final class HighlightsFetcher extends AsyncTask<Void, Void, List<HighlightModel>> {
private final String id; private final String id;
private final boolean storiesig;
private final FetchListener<List<HighlightModel>> fetchListener; private final FetchListener<List<HighlightModel>> fetchListener;
public HighlightsFetcher(final String id, final boolean storiesig, final FetchListener<List<HighlightModel>> fetchListener) { public HighlightsFetcher(final String id, final FetchListener<List<HighlightModel>> fetchListener) {
this.id = id; this.id = id;
this.storiesig = storiesig;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@Override @Override
protected List<HighlightModel> doInBackground(final Void... voids) { protected List<HighlightModel> doInBackground(final Void... voids) {
List<HighlightModel> result = null; List<HighlightModel> result = null;
String url = "https://" + (storiesig ? "storiesig" : "i.instagram") + ".com/api/v1/highlights/" + id + "/highlights_tray/"; String url = "https://i.instagram.com/api/v1/highlights/" + id + "/highlights_tray/";
try { try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false); conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false); conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT); conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -28,18 +30,18 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> { public final class PostsFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "PostsFetcher"; private static final String TAG = "PostsFetcher";
private final PostItemType type; private final PostItemType type;
private final String endCursor; private final String endCursor;
private final String id; private final String id;
private final FetchListener<PostModel[]> fetchListener; private final FetchListener<List<PostModel>> fetchListener;
private String username = null; private String username = null;
public PostsFetcher(final String id, public PostsFetcher(final String id,
final PostItemType type, final PostItemType type,
final String endCursor, final String endCursor,
final FetchListener<PostModel[]> fetchListener) { final FetchListener<List<PostModel>> fetchListener) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.endCursor = endCursor == null ? "" : endCursor; this.endCursor = endCursor == null ? "" : endCursor;
@ -52,7 +54,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
@Override @Override
protected PostModel[] doInBackground(final Void... voids) { protected List<PostModel> doInBackground(final Void... voids) {
// final boolean isHashTag = id.charAt(0) == '#'; // final boolean isHashTag = id.charAt(0) == '#';
// final boolean isSaved = id.charAt(0) == '$'; // final boolean isSaved = id.charAt(0) == '$';
// final boolean isTagged = id.charAt(0) == '%'; // final boolean isTagged = id.charAt(0) == '%';
@ -79,7 +81,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
default: default:
url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor;
} }
PostModel[] result = null; List<PostModel> result = new ArrayList<>();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false); conn.setUseCaches(false);
@ -126,8 +128,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
final JSONArray edges = mediaPosts.getJSONArray("edges"); final JSONArray edges = mediaPosts.getJSONArray("edges");
final PostModel[] models = new PostModel[edges.length()]; for (int i = 0; i < edges.length(); ++i) {
for (int i = 0; i < models.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges");
@ -139,34 +140,43 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE; else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), final PostModel model = new PostModel(
mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"), itemType,
mediaNode.getString(Constants.EXTRAS_SHORTCODE), mediaNode.getString(Constants.EXTRAS_ID),
captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null, mediaNode.getString("display_url"),
mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"), mediaNode.getString("thumbnail_src"),
mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count")); mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0)
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); .getJSONObject("node")
.getString("text")
: null,
mediaNode.getLong("taken_at_timestamp"),
mediaNode.optBoolean("viewer_has_liked"),
mediaNode.optBoolean("viewer_has_saved"),
mediaNode.getJSONObject("edge_liked_by")
.getLong("count")
);
result.add(model);
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
} }
if (models.length != 0 && models[models.length - 1] != null) if (!result.isEmpty() && result.get(result.size() - 1) != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor); result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor);
result = models;
} }
conn.disconnect(); conn.disconnect();
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e(TAG, "", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error fetching posts", e);
}
} }
return result; return result;
} }
@Override @Override
protected void onPostExecute(final PostModel[] postModels) { protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels); if (fetchListener != null) fetchListener.onResult(postModels);
} }
} }

View File

@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String defaultHashTagPic = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
final JSONObject jsonObject = new JSONObject(NetworkUtils.readFromConnection(conn)); final JSONObject jsonObject = new JSONObject(NetworkUtils.readFromConnection(conn));
conn.disconnect(); conn.disconnect();
@ -63,7 +62,7 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
suggestionModels.add(new SuggestionModel(false, suggestionModels.add(new SuggestionModel(false,
hashtag.getString(Constants.EXTRAS_NAME), hashtag.getString(Constants.EXTRAS_NAME),
null, null,
hashtag.optString("profile_pic_url", defaultHashTagPic), hashtag.optString("profile_pic_url", Constants.DEFAULT_HASH_TAG_PIC),
SuggestionType.TYPE_HASHTAG, SuggestionType.TYPE_HASHTAG,
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1))); hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
} }

View File

@ -1,4 +1,4 @@
package awais.instagrabber.asyncs; package awais.instagrabber.asyncs.direct_messages;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
@ -9,7 +9,6 @@ import java.io.DataOutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.NetworkUtils; import awais.instagrabber.utils.NetworkUtils;
@ -17,16 +16,16 @@ import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class CommentAction extends AsyncTask<Void, Void, String> { public class CreateThreadAction extends AsyncTask<Void, Void, String> {
private static final String TAG = "CommentAction"; private static final String TAG = "CommentAction";
private final String cookie; private final String cookie;
private final StoryModel storyModel; private final String userId;
private final OnTaskCompleteListener onTaskCompleteListener; private final OnTaskCompleteListener onTaskCompleteListener;
public CommentAction(final String cookie, final StoryModel storyModel, final OnTaskCompleteListener onTaskCompleteListener) { public CreateThreadAction(final String cookie, final String userId, final OnTaskCompleteListener onTaskCompleteListener) {
this.cookie = cookie; this.cookie = cookie;
this.storyModel = storyModel; this.userId = userId;
this.onTaskCompleteListener = onTaskCompleteListener; this.onTaskCompleteListener = onTaskCompleteListener;
} }
@ -41,7 +40,7 @@ public class CommentAction extends AsyncTask<Void, Void, String> {
final String urlParameters = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0] final String urlParameters = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+ "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie) + "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie)
+ "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID) + "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+ "\",\"recipient_users\":\"[" + storyModel.getUserId() // <- string of array of number (not joking) + "\",\"recipient_users\":\"[" + userId // <- string of array of number (not joking)
+ "]\"}"); + "]\"}");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (urlParameters != null) { if (urlParameters != null) {

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -28,25 +30,27 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> { public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private final String endCursor; private static final String TAG = "iLikedFetcher";
private final FetchListener<PostModel[]> fetchListener;
public iLikedFetcher(final FetchListener<PostModel[]> fetchListener) { private final String endCursor;
private final FetchListener<List<PostModel>> fetchListener;
public iLikedFetcher(final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = ""; this.endCursor = "";
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
public iLikedFetcher(final String endCursor, final FetchListener<PostModel[]> fetchListener) { public iLikedFetcher(final String endCursor, final FetchListener<List<PostModel>> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor; this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@Override @Override
protected PostModel[] doInBackground(final Void... voids) { protected List<PostModel> doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id="+endCursor; final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor;
PostModel[] result = null; List<PostModel> result = new ArrayList<>();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false); conn.setUseCaches(false);
@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
} }
final JSONArray edges = body.getJSONArray("items"); final JSONArray edges = body.getJSONArray("items");
final PostModel[] models = new PostModel[edges.length()]; for (int i = 0; i < edges.length(); ++i) {
for (int i = 0; i < models.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i); final JSONObject mediaNode = edges.getJSONObject(i);
final boolean isSlider = mediaNode.has("carousel_media_count"); final boolean isSlider = mediaNode.has("carousel_media_count");
@ -80,48 +83,57 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE; else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), final PostModel model = new PostModel(
isSlider itemType,
? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) mediaNode.getString(Constants.EXTRAS_ID),
: ResponseBodyUtils.getHighQualityImage(mediaNode), isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media")
isSlider .getJSONObject(0))
? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) : ResponseBodyUtils.getHighQualityImage(mediaNode),
: ResponseBodyUtils.getLowQualityImage(mediaNode), isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
mediaNode.getString("code"), mediaNode.getString("code"),
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"), mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"), true, mediaNode.getLong("taken_at"),
mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count")); true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username"); String username = mediaNode.getJSONObject("user").getString("username");
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null; File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath); if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
} }
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
} }
if (models[models.length - 1] != null) final int length = result.size();
models[models.length - 1].setPageCursor(hasNextPage, endCursor); if (length >= 1 && result.get(length - 1) != null) {
result.get(length - 1).setPageCursor(hasNextPage, endCursor);
result = models; }
} }
conn.disconnect(); conn.disconnect();
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
} }
return result; return result;
} }
@Override @Override
protected void onPostExecute(final PostModel[] postModels) { protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels); if (fetchListener != null) fetchListener.onResult(postModels);
} }
} }

View File

@ -30,7 +30,6 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
private String username; private String username;
private final boolean isLoc; private final boolean isLoc;
private final boolean isHashtag; private final boolean isHashtag;
private final boolean storiesig;
private final boolean highlight; private final boolean highlight;
private final FetchListener<StoryModel[]> fetchListener; private final FetchListener<StoryModel[]> fetchListener;
@ -38,14 +37,12 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
final String username, final String username,
final boolean isLoc, final boolean isLoc,
final boolean isHashtag, final boolean isHashtag,
final boolean storiesig,
final boolean highlight, final boolean highlight,
final FetchListener<StoryModel[]> fetchListener) { final FetchListener<StoryModel[]> fetchListener) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.isLoc = isLoc; this.isLoc = isLoc;
this.isHashtag = isHashtag; this.isHashtag = isHashtag;
this.storiesig = storiesig;
this.highlight = highlight; this.highlight = highlight;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@ -55,13 +52,7 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
StoryModel[] result = null; StoryModel[] result = null;
final String userId = id.replace(":", "%3A"); final String userId = id.replace(":", "%3A");
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append("https://"); builder.append("https://i.instagram.com/api/v1/");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
if (isLoc) { if (isLoc) {
builder.append("locations/"); builder.append("locations/");
} }
@ -75,24 +66,20 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
} }
builder.append(userId); builder.append(userId);
if (!highlight) { if (!highlight) {
if (storiesig) { builder.append("/story/");
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
} }
final String url = builder.toString(); final String url = builder.toString();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(true); conn.setInstanceFollowRedirects(true);
conn.setUseCaches(false); conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT); conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8"); conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
conn.connect(); conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)); JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn));
if (!storiesig && !highlight) if (!highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel"); data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
else if (highlight) data = data.getJSONObject("reels").optJSONObject(id); else if (highlight) data = data.getJSONObject("reels").optJSONObject(id);

View File

@ -0,0 +1,169 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import java.util.Locale;
import awais.instagrabber.databinding.DialogCreateBackupBinding;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.DownloadUtils.PERMS;
public class CreateBackupDialogFragment extends DialogFragment {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private final OnResultListener onResultListener;
private DialogCreateBackupBinding binding;
public CreateBackupDialogFragment(final OnResultListener onResultListener) {
this.onResultListener = onResultListener;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = DialogCreateBackupBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final Window window = dialog.getWindow();
if (window == null) return;
final int height = ViewGroup.LayoutParams.WRAP_CONTENT;
final int width = (int) (Utils.displayMetrics.widthPixels * 0.8);
window.setLayout(width, height);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
private void init() {
binding.etPassword.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.btnSaveTo.setEnabled(!TextUtils.isEmpty(s));
}
@Override
public void afterTextChanged(final Editable s) {}
});
final Context context = getContext();
if (context == null) {
return;
}
binding.cbPassword.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
if (TextUtils.isEmpty(binding.etPassword.getText())) {
binding.btnSaveTo.setEnabled(false);
}
binding.passwordField.setVisibility(View.VISIBLE);
binding.etPassword.requestFocus();
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
return;
}
binding.btnSaveTo.setEnabled(true);
binding.passwordField.setVisibility(View.GONE);
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
});
binding.btnSaveTo.setOnClickListener(v -> {
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
showChooser(context);
} else {
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
}
});
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
final Context context = getContext();
if (context == null) return;
showChooser(context);
}
}
private void showChooser(@NonNull final Context context) {
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
final Editable passwordText = binding.etPassword.getText();
final String password = binding.cbPassword.isChecked()
&& passwordText != null
&& !TextUtils.isEmpty(passwordText.toString())
? passwordText.toString().trim()
: null;
final DirectoryChooser directoryChooser = new DirectoryChooser()
.setInitialDirectory(folderPath)
.setInteractionListener(path -> {
final File file = new File(path, String.format(Locale.ENGLISH, "barinsta_%d.backup", System.currentTimeMillis()));
int flags = 0;
if (binding.cbExportFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
}
if (binding.cbExportSettings.isChecked()) {
flags |= ExportImportUtils.FLAG_SETTINGS;
}
if (binding.cbExportLogins.isChecked()) {
flags |= ExportImportUtils.FLAG_COOKIES;
}
ExportImportUtils.exportData(password, flags, file, result -> {
if (onResultListener != null) {
onResultListener.onResult(result);
}
dismiss();
}, context);
});
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
}
public interface OnResultListener {
void onResult(boolean result);
}
}

View File

@ -1,181 +0,0 @@
package awais.instagrabber.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.SimpleAdapter;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener,
View.OnClickListener, View.OnLongClickListener {
private boolean cookieChanged, isQuery;
private Activity activity;
private String userQuery, displayName;
private View btnFavorite, btnImportExport;
private SimpleAdapter<DataBox.FavoriteModel> favoritesAdapter;
private RecyclerView rvFavorites, rvQuickAccess;
public QuickAccessDialog setQuery(final String userQuery, final String displayName) {
this.userQuery = userQuery;
this.displayName = displayName;
return this;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setOnShowListener(this);
final Context context = getContext();
activity = context instanceof Activity ? (Activity) context : getActivity();
final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null);
btnFavorite = contentView.findViewById(R.id.btnFavorite);
btnImportExport = contentView.findViewById(R.id.importExport);
isQuery = !TextUtils.isEmpty(userQuery);
btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE);
Utils.setTooltipText(btnImportExport, R.string.import_export);
favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this);
btnFavorite.setOnClickListener(this);
btnImportExport.setOnClickListener(this);
rvFavorites = contentView.findViewById(R.id.rvFavorites);
rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess);
final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL);
rvFavorites.addItemDecoration(itemDecoration);
rvFavorites.setAdapter(favoritesAdapter);
final String cookieStr = settingsHelper.getString(Constants.COOKIE);
if (!TextUtils.isEmpty(cookieStr)
|| Utils.dataBox.getCookieCount() > 0 // fallback for export / import
) {
rvQuickAccess.addItemDecoration(itemDecoration);
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (!TextUtils.isEmpty(cookieStr) && allCookies != null) {
for (final DataBox.CookieModel cookie : allCookies) {
if (cookieStr.equals(cookie.getCookie())) {
cookie.setSelected(true);
break;
}
}
}
rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this));
} else {
((View) rvQuickAccess.getParent()).setVisibility(View.GONE);
}
dialog.setContentView(contentView);
return dialog;
}
@Override
public void onClick(@NonNull final View v) {
final Object tag = v.getTag();
if (v == btnFavorite) {
if (isQuery) {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis(), displayName));
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites());
}
} else if (v == btnImportExport) {
if (ContextCompat.checkSelfPermission(activity, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(DownloadUtils.PERMS, 6007);
else Utils.showImportExportDialog(v.getContext());
} else if (tag instanceof DataBox.FavoriteModel) {
// if (MainActivityBackup.scanHack != null) {
// MainActivityBackup.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery());
// dismiss();
// }
} else if (tag instanceof DataBox.CookieModel) {
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag;
if (!cookieModel.isSelected()) {
settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie());
CookieUtils.setupCookies(cookieModel.getCookie());
cookieChanged = true;
}
dismiss();
}
}
@Override
public boolean onLongClick(@NonNull final View v) {
final Object tag = v.getTag();
if (tag instanceof DataBox.FavoriteModel) {
final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag;
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> {
Utils.dataBox.delFavorite(favoriteModel);
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites());
})
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete,
favoriteModel.getQuery())).show();
} else if (tag instanceof DataBox.CookieModel) {
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag;
if (cookieModel.isSelected())
Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show();
else
new AlertDialog.Builder(activity)
.setMessage(getString(R.string.quick_access_confirm_delete, cookieModel.getUsername()))
.setPositiveButton(R.string.yes, (d, which) -> {
Utils.dataBox.delUserCookie(cookieModel);
rvQuickAccess.findViewWithTag(cookieModel).setVisibility(View.GONE);
})
.setNegativeButton(R.string.no, null)
.show();
}
return true;
}
@Override
public void onDismiss(@NonNull final DialogInterface dialog) {
super.onDismiss(dialog);
if (cookieChanged && activity != null) activity.recreate();
}
@Override
public void onShow(final DialogInterface dialog) {
if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG))
new AlertDialog.Builder(activity)
.setMessage(R.string.quick_access_info_dialog)
.setPositiveButton(R.string.ok, null)
.setNeutralButton(R.string.dont_show_again, (d, which) ->
settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show();
}
}

View File

@ -0,0 +1,180 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import awais.instagrabber.databinding.DialogRestoreBackupBinding;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.DownloadUtils.PERMS;
public class RestoreBackupDialogFragment extends DialogFragment {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private final OnResultListener onResultListener;
private DialogRestoreBackupBinding binding;
private File file;
private boolean isEncrypted;
public RestoreBackupDialogFragment(final OnResultListener onResultListener) {
this.onResultListener = onResultListener;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = DialogRestoreBackupBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final Window window = dialog.getWindow();
if (window == null) return;
final int height = ViewGroup.LayoutParams.WRAP_CONTENT;
final int width = (int) (Utils.displayMetrics.widthPixels * 0.8);
window.setLayout(width, height);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showChooser();
}
}
private void init() {
final Context context = getContext();
if (context == null) {
return;
}
binding.btnRestore.setEnabled(false);
binding.btnRestore.setOnClickListener(v -> {
int flags = 0;
if (binding.cbFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
}
if (binding.cbSettings.isChecked()) {
flags |= ExportImportUtils.FLAG_SETTINGS;
}
if (binding.cbAccounts.isChecked()) {
flags |= ExportImportUtils.FLAG_COOKIES;
}
final Editable text = binding.etPassword.getText();
if (isEncrypted && text == null) return;
try {
ExportImportUtils.importData(
context,
flags,
file,
!isEncrypted ? null : text.toString(),
result -> {
if (onResultListener != null) {
onResultListener.onResult(result);
}
dismiss();
}
);
} catch (IncorrectPasswordException e) {
binding.passwordField.setError("Incorrect password");
}
});
binding.etPassword.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.btnRestore.setEnabled(!TextUtils.isEmpty(s));
binding.passwordField.setError(null);
}
@Override
public void afterTextChanged(final Editable s) {}
});
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
showChooser();
return;
}
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
}
private void showChooser() {
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
final Context context = getContext();
if (context == null) return;
final DirectoryChooser directoryChooser = new DirectoryChooser()
.setInitialDirectory(folderPath)
.setShowBackupFiles(true)
.setInteractionListener(file -> {
isEncrypted = ExportImportUtils.isEncrypted(file);
if (isEncrypted) {
binding.passwordGroup.setVisibility(View.VISIBLE);
binding.passwordGroup.post(() -> {
binding.etPassword.requestFocus();
binding.etPassword.post(() -> {
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
});
binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
});
} else {
binding.passwordGroup.setVisibility(View.GONE);
binding.btnRestore.setEnabled(true);
}
this.file = file;
binding.filePath.setText(file.getAbsolutePath());
});
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setOnCancelListener(this::dismiss);
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
}
public interface OnResultListener {
void onResult(boolean result);
}
}

View File

@ -0,0 +1,215 @@
package awais.instagrabber.fragments;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FavoritesAdapter;
import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.databinding.FragmentFavoritesBinding;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FavoritesViewModel;
public class FavoritesFragment extends Fragment {
private static final String TAG = "FavoritesFragment";
private boolean shouldRefresh = true;
private FragmentFavoritesBinding binding;
private RecyclerView root;
private FavoritesViewModel favoritesViewModel;
private FavoritesAdapter adapter;
@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 = FragmentFavoritesBinding.inflate(getLayoutInflater());
root = binding.getRoot();
binding.favoriteList.setLayoutManager(new LinearLayoutManager(getContext()));
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
init();
shouldRefresh = false;
}
@Override
public void onResume() {
super.onResume();
if (favoritesViewModel == null || adapter == null) return;
// refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh
favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
favoritesViewModel.getList().postValue(allFavorites);
fetchMissingInfo(allFavorites);
}
private void init() {
favoritesViewModel = new ViewModelProvider(this).get(FavoritesViewModel.class);
adapter = new FavoritesAdapter(model -> {
// navigate
switch (model.getType()) {
case USER: {
final String username = model.getQuery();
// Log.d(TAG, "username: " + username);
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", "@" + username);
navController.navigate(R.id.action_global_profileFragment, bundle);
break;
}
case LOCATION: {
final String locationId = model.getQuery();
// Log.d(TAG, "locationId: " + locationId);
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("locationId", locationId);
navController.navigate(R.id.action_global_locationFragment, bundle);
break;
}
case HASHTAG: {
final String hashtag = model.getQuery();
// Log.d(TAG, "hashtag: " + hashtag);
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("hashtag", "#" + hashtag);
navController.navigate(R.id.action_global_hashTagFragment, bundle);
break;
}
default:
// do nothing
}
}, model -> {
// delete
final Context context = getContext();
if (context == null) return false;
new MaterialAlertDialogBuilder(context)
.setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery()))
.setPositiveButton(R.string.yes, (d, which) -> {
Utils.dataBox.deleteFavorite(model.getQuery(), model.getType());
d.dismiss();
favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites());
})
.setNegativeButton(R.string.no, null)
.show();
return true;
});
binding.favoriteList.setAdapter(adapter);
// favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
private void fetchMissingInfo(final List<DataBox.FavoriteModel> allFavorites) {
final Runnable runnable = () -> {
final List<DataBox.FavoriteModel> updatedList = new ArrayList<>(allFavorites);
// cyclic barrier is to make the async calls synchronous
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
// Log.d(TAG, "fetchMissingInfo: barrier action");
favoritesViewModel.getList().postValue(new ArrayList<>(updatedList));
});
try {
for (final DataBox.FavoriteModel model : allFavorites) {
cyclicBarrier.reset();
// if the model has missing pic or display name (for user and location), fetch those details
switch (model.getType()) {
case LOCATION:
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new LocationFetcher(model.getQuery(), result -> {
try {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated);
} finally {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
}).execute();
cyclicBarrier.await();
}
break;
case USER:
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new ProfileFetcher(model.getQuery(), result -> {
try {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated);
} finally {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
}).execute();
cyclicBarrier.await();
}
break;
case HASHTAG:
default:
// hashtags don't require displayName or pic
// updatedList.add(model);
}
}
} catch (Exception e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
favoritesViewModel.getList().postValue(updatedList);
};
new Thread(runnable).start();
}
}

View File

@ -1,7 +1,6 @@
package awais.instagrabber.fragments; package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel; import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class HashTagFragment extends Fragment { public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment"; private static final String TAG = "HashTagFragment";
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment {
private String endCursor; private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; private boolean isLoggedIn;
private StoryModel[] storyModels; private TagsService tagsService;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment {
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return; if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); List<PostModel> finalList = postModels == null || postModels.isEmpty()
finalList.addAll(Arrays.asList(result)); ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
finalList.addAll(result);
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
PostModel model = null; PostModel model = null;
if (result.length != 0) { if (!result.isEmpty()) {
model = result[result.length - 1]; model = result.get(result.size() - 1);
} }
if (model == null) return; if (model == null) return;
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment {
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance();
} }
@Nullable @Nullable
@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init(); init();
shouldRefresh = false; shouldRefresh = false;
} }
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchHashtagModel();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment {
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); stopCurrentExecutor();
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
if (TextUtils.isEmpty(hashtag)) return; if (TextUtils.isEmpty(hashtag)) return;
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener) currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
@ -265,30 +286,110 @@ public class HashTagFragment extends Fragment {
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
if (isLoggedIn) { if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> { new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) { if (stories != null && stories.length > 0) {
binding.mainHashtagImage.setStoriesBorder(); binding.mainHashtagImage.setStoriesBorder();
} }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow); binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
ContextCompat.getColor(context, hashtagModel.getFollowing() ? R.drawable.ic_outline_person_add_disabled_24
? R.color.btn_purple_background : R.drawable.ic_outline_person_add_24);
: R.color.btn_pink_background))); binding.btnFollowTag.setOnClickListener(v -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.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;
}
tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.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 { } else {
binding.btnFollowTag.setText(Utils.dataBox.getFavorite(hashtag) != null binding.btnFollowTag.setVisibility(View.GONE);
? R.string.unfavorite_short
: R.string.favorite_short);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf(
ContextCompat.getColor(context, Utils.dataBox.getFavorite(hashtag) != null
? R.color.btn_purple_background
: R.color.btn_pink_background)));
} }
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFav = favorite != null;
binding.favChip.setVisibility(View.VISIBLE);
binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
binding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
binding.favChip.setText(R.string.add_to_favorites);
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1,
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
null,
new Date()
));
binding.favChip.setText(R.string.favorite_short);
binding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
}
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())
.show();
});
binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
final String postCount = String.valueOf(hashtagModel.getPostCount()); final String postCount = String.valueOf(hashtagModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount)); final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
binding.mainTagPostCount.setText(span); binding.mainTagPostCount.setText(span);
@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment {
if (actionBar != null) { if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag); Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler(); final Handler handler = new Handler();
handler.postDelayed(() -> { handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
actionBar.setTitle(hashtag);
}, 200);
} }
} }

View File

@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -62,7 +67,7 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class LocationFragment extends Fragment { public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment"; private static final String TAG = "LocationFragment";
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
@ -78,7 +83,7 @@ public class LocationFragment extends Fragment {
private String endCursor; private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; private boolean isLoggedIn;
private StoryModel[] storyModels; private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
@ -119,20 +124,25 @@ public class LocationFragment extends Fragment {
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return; if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels); : new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result)); if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
PostModel model = null; PostModel model = null;
if (result.length != 0) { if (!result.isEmpty()) {
model = result[result.length - 1]; model = result.get(result.size() - 1);
} }
if (model == null) return; if (model == null) return;
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
@ -164,10 +174,18 @@ public class LocationFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init(); init();
shouldRefresh = false; shouldRefresh = false;
} }
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchLocationModel();
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -182,6 +200,8 @@ public class LocationFragment extends Fragment {
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
locationId = fragmentArgs.getLocationId(); locationId = fragmentArgs.getLocationId();
binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE);
setTitle(); setTitle();
setupPosts(); setupPosts();
fetchLocationModel(); fetchLocationModel();
@ -273,9 +293,7 @@ public class LocationFragment extends Fragment {
true, true,
false, false,
false, false,
false,
stories -> { stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) { if (stories != null && stories.length > 0) {
binding.mainLocationImage.setStoriesBorder(); binding.mainLocationImage.setStoriesBorder();
} }
@ -283,7 +301,7 @@ public class LocationFragment extends Fragment {
} }
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount()); final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline,
postCount)); postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
@ -329,6 +347,40 @@ public class LocationFragment extends Fragment {
binding.locationUrl.setVisibility(View.VISIBLE); binding.locationUrl.setVisibility(View.VISIBLE);
binding.locationUrl.setText(TextUtils.getSpannableUrl(url)); binding.locationUrl.setText(TextUtils.getSpannableUrl(url));
} }
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFav = favorite != null;
binding.favChip.setVisibility(View.VISIBLE);
binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
binding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION);
binding.favChip.setText(R.string.add_to_favorites);
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1,
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
locationModel.getSdProfilePic(),
new Date()
));
binding.favChip.setText(R.string.favorite_short);
binding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
}
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())
.show();
});
} }
private void fetchPosts() { private void fetchPosts() {

View File

@ -27,7 +27,6 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -58,7 +57,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private static AsyncTask<?, ?, ?> currentlyExecuting; private static AsyncTask<?, ?, ?> currentlyExecuting;
private PostsAdapter postsAdapter; private PostsAdapter postsAdapter;
private boolean hasNextPage; private boolean hasNextPage;
private boolean autoloadPosts;
private FragmentSavedBinding binding; private FragmentSavedBinding binding;
private String username; private String username;
private String endCursor; private String endCursor;
@ -107,17 +105,16 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
final List<PostModel> current = postsViewModel.getList().getValue(); final List<PostModel> current = postsViewModel.getList().getValue();
if (result != null && result.length > 0) { if (result != null && !result.isEmpty()) {
final List<PostModel> resultList = Arrays.asList(result);
if (current == null) { if (current == null) {
postsViewModel.getList().postValue(resultList); postsViewModel.getList().postValue(result);
} else { } else {
final List<PostModel> currentCopy = new ArrayList<>(current); final List<PostModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(resultList); currentCopy.addAll(result);
postsViewModel.getList().postValue(currentCopy); postsViewModel.getList().postValue(currentCopy);
} }
binding.mainPosts.post(() -> { binding.mainPosts.post(() -> {
@ -125,11 +122,11 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
binding.mainPosts.setVisibility(View.VISIBLE); binding.mainPosts.setVisibility(View.VISIBLE);
}); });
final PostModel model = result.length > 0 ? result[result.length - 1] : null; final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null;
if (model != null) { if (model != null) {
endCursor = model.getEndCursor(); endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage(); hasNextPage = model.hasNextPage();
if (autoloadPosts && hasNextPage) { if (hasNextPage) {
fetchPosts(); fetchPosts();
} else { } else {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
@ -246,7 +243,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!autoloadPosts && hasNextPage) { if (hasNextPage) {
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts(); fetchPosts();
endCursor = null; endCursor = null;
@ -258,7 +255,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); stopCurrentExecutor();
final AsyncTask<Void, Void, PostModel[]> asyncTask; final AsyncTask<Void, Void, List<PostModel>> asyncTask;
switch (type) { switch (type) {
case LIKED: case LIKED:
asyncTask = new iLikedFetcher(endCursor, postsFetchListener); asyncTask = new iLikedFetcher(endCursor, postsFetchListener);

View File

@ -62,12 +62,12 @@ import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter; import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.CommentAction;
import awais.instagrabber.asyncs.DownloadAsync; import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.QuizAction; import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction; import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction; import awais.instagrabber.asyncs.SeenAction;
import awais.instagrabber.asyncs.VoteAction; import awais.instagrabber.asyncs.VoteAction;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster; import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.SwipeGestureListener; import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.FragmentStoryViewerBinding; import awais.instagrabber.databinding.FragmentStoryViewerBinding;
@ -196,7 +196,7 @@ public class StoryViewerFragment extends Fragment {
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.reply_story) .setTitle(R.string.reply_story)
.setView(input) .setView(input)
.setPositiveButton(R.string.ok, (d, w) -> new CommentAction(cookie, currentStory, threadId -> { .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> {
try { try {
final DirectThreadBroadcaster.StoryReplyBroadcastOptions options = new DirectThreadBroadcaster.StoryReplyBroadcastOptions( final DirectThreadBroadcaster.StoryReplyBroadcastOptions options = new DirectThreadBroadcaster.StoryReplyBroadcastOptions(
input.getText().toString(), input.getText().toString(),
@ -544,7 +544,6 @@ public class StoryViewerFragment extends Fragment {
}; };
storiesService.getUserStory(currentStoryMediaId, storiesService.getUserStory(currentStoryMediaId,
username, username,
!isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER) == StoryViewerChoice.STORIESIG.getValue(),
false, false,
false, false,
isHighlight, isHighlight,

View File

@ -325,6 +325,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
binding.feedSwipeRefreshLayout.setRefreshing(false);
if (videoAwareRecyclerScroller != null && shouldAutoPlay) { if (videoAwareRecyclerScroller != null && shouldAutoPlay) {
videoAwareRecyclerScroller.startPlaying(); videoAwareRecyclerScroller.startPlaying();
} }

View File

@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -30,8 +29,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.ProfileNavGraphDirections;
@ -56,6 +56,7 @@ import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.UsernameFetcher; import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher; import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
@ -71,6 +72,7 @@ import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.models.enums.StoryViewerChoice; import awais.instagrabber.models.enums.StoryViewerChoice;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -111,11 +113,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private StoryModel[] storyModels; private StoryModel[] storyModels;
private boolean hasNextPage; private boolean hasNextPage;
private String endCursor; private String endCursor;
private AsyncTask<Void, Void, PostModel[]> currentlyExecuting; private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private MenuItem favMenuItem;
private boolean isPullToRefresh; private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter; private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel; private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem;
private MenuItem restrictMenuItem;
private final Runnable usernameSettingRunnable = () -> { private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -161,11 +164,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return false; return false;
} }
}); });
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.length <= 0) { if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel); binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc); binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
@ -175,15 +178,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final List<PostModel> postModels = postsViewModel.getList().getValue(); final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels); : new ArrayList<>(postModels);
final List<PostModel> resultList = Arrays.asList(result);
if (isPullToRefresh) { if (isPullToRefresh) {
finalList = resultList; finalList = result;
isPullToRefresh = false; isPullToRefresh = false;
} else { } else {
finalList.addAll(resultList); finalList.addAll(result);
} }
postsViewModel.getList().postValue(finalList); postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result[result.length - 1]; final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return; if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor(); endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage(); hasNextPage = lastPostModel.hasNextPage();
@ -262,7 +264,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu); inflater.inflate(R.menu.profile_menu, menu);
favMenuItem = menu.findItem(R.id.favourites); // favMenuItem = menu.findItem(R.id.favourites);
blockMenuItem = menu.findItem(R.id.block);
if (blockMenuItem != null) {
blockMenuItem.setVisible(false);
}
restrictMenuItem = menu.findItem(R.id.restrict);
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(false);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
friendshipService.toggleRestrict(
profileModel.getId(),
!profileModel.getRestricted(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoRestrictRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoRestrictRootResponse result) {
Log.d(TAG, action + " success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error while performing " + action, t);
}
});
return true;
}
if (item.getItemId() == R.id.block) {
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
if (!isLoggedIn) return false;
if (profileModel.getBlocked()) {
friendshipService.unblock(
userIdFromCookie,
profileModel.getId(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unblock success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unblocking", t);
}
});
return true;
}
friendshipService.block(
userIdFromCookie,
profileModel.getId(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Block success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error blocking", t);
}
});
return true;
}
return super.onOptionsItemSelected(item);
} }
@Override @Override
@ -295,6 +371,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
if (TextUtils.isEmpty(username) && !isLoggedIn) { if (TextUtils.isEmpty(username) && !isLoggedIn) {
binding.infoContainer.setVisibility(View.GONE); binding.infoContainer.setVisibility(View.GONE);
binding.swipeRefreshLayout.setEnabled(false);
binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24); binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24);
binding.privatePage2.setText(R.string.no_acc); binding.privatePage2.setText(R.string.no_acc);
final NestedCoordinatorLayout.LayoutParams layoutParams = (NestedCoordinatorLayout.LayoutParams) binding.privatePage.getLayoutParams(); final NestedCoordinatorLayout.LayoutParams layoutParams = (NestedCoordinatorLayout.LayoutParams) binding.privatePage.getLayoutParams();
@ -303,6 +380,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
return; return;
} }
binding.swipeRefreshLayout.setEnabled(true);
setupPosts(); setupPosts();
setupHighlights(); setupHighlights();
setupCommonListeners(); setupCommonListeners();
@ -337,17 +415,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void fetchProfileDetails() { private void fetchProfileDetails() {
if (TextUtils.isEmpty(username)) return;
new ProfileFetcher(username.substring(1), profileModel -> { new ProfileFetcher(username.substring(1), profileModel -> {
if (getContext() == null) return; if (getContext() == null) return;
this.profileModel = profileModel; this.profileModel = profileModel;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); // final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
final boolean isSelf = isLoggedIn // final boolean isSelf = isLoggedIn
&& profileModel != null // && profileModel != null
&& userIdFromCookie != null // && userIdFromCookie != null
&& userIdFromCookie.equals(profileModel.getId()); // && userIdFromCookie.equals(profileModel.getId());
if (favMenuItem != null) { // if (favMenuItem != null) {
favMenuItem.setVisible(isSelf); // favMenuItem.setVisible(isSelf);
} // }
setProfileDetails(); setProfileDetails();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -363,12 +442,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId(); final String profileId = profileModel.getId();
if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()) || isLoggedIn) { if (isLoggedIn) {
new iStoryStatusFetcher(profileId, new iStoryStatusFetcher(profileId,
profileModel.getUsername(), profileModel.getUsername(),
false, false,
false, false,
!isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()),
false, false,
result -> { result -> {
storyModels = result; storyModels = result;
@ -377,7 +455,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, new HighlightsFetcher(profileId,
!isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()),
result -> { result -> {
if (result != null) { if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE); binding.highlightsList.setVisibility(View.VISIBLE);
@ -385,7 +462,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else binding.highlightsList.setVisibility(View.GONE); } else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) { } else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) {
Log.d("austin_debug", "alo triggered"); // Log.d(TAG, "alo triggered");
aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback<List<StoryModel>>() { aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback<List<StoryModel>>() {
@Override @Override
public void onSuccess(final List<StoryModel> result) { public void onSuccess(final List<StoryModel> result) {
@ -402,74 +479,66 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}); });
} }
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) { if (isLoggedIn) {
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (profileId.equals(myId)) { if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE); binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE); binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE); binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved); binding.btnSaved.setText(R.string.saved);
ViewCompat.setBackgroundTintList(binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} else { } else {
binding.btnTagged.setVisibility(View.GONE); binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE); binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE); binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE); binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) { if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow); binding.btnFollow.setText(R.string.unfollow);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else if (profileModel.getRequested()) { } else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel); binding.btnFollow.setText(R.string.cancel);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else { } else {
binding.btnFollow.setText(R.string.follow); binding.btnFollow.setText(R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollow, binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
} }
binding.btnRestrict.setVisibility(View.VISIBLE); if (restrictMenuItem != null) {
if (profileModel.getRestricted()) { restrictMenuItem.setVisible(true);
binding.btnRestrict.setText(R.string.unrestrict); if (profileModel.getRestricted()) {
ViewCompat.setBackgroundTintList(binding.btnRestrict, restrictMenuItem.setTitle(R.string.unrestrict);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); } else {
} else { restrictMenuItem.setTitle(R.string.restrict);
binding.btnRestrict.setText(R.string.restrict); }
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} }
binding.btnBlock.setVisibility(View.VISIBLE); binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
binding.btnTagged.setVisibility(View.VISIBLE); if (blockMenuItem != null) {
if (profileModel.getBlocked()) { blockMenuItem.setVisible(true);
binding.btnBlock.setText(R.string.unblock); if (profileModel.getBlocked()) {
ViewCompat.setBackgroundTintList(binding.btnBlock, blockMenuItem.setTitle(R.string.unblock);
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); } else {
} else { blockMenuItem.setTitle(R.string.block);
binding.btnBlock.setText(R.string.block); }
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background)));
} }
} }
} else { } else {
if (Utils.dataBox.getFavorite(username) != null) { if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
binding.btnFollow.setText(R.string.unfavorite_short); restrictMenuItem.setVisible(true);
ViewCompat.setBackgroundTintList(binding.btnFollow, if (profileModel.getRestricted()) {
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); restrictMenuItem.setTitle(R.string.unrestrict);
} else { } else {
binding.btnFollow.setText(R.string.favorite_short); restrictMenuItem.setTitle(R.string.restrict);
ViewCompat.setBackgroundTintList(binding.btnFollow, }
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
}
binding.btnFollow.setVisibility(View.VISIBLE);
if (!profileModel.isReallyPrivate()) {
binding.btnRestrict.setVisibility(View.VISIBLE);
binding.btnRestrict.setText(R.string.tagged);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_blue_background)));
} }
} }
if (!profileId.equals(myId)) {
binding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null;
binding.favCb.setChecked(isFav);
binding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24);
} else {
binding.favCb.setVisibility(View.GONE);
}
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic()); binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
final long followersCount = profileModel.getFollowersCount(); final long followersCount = profileModel.getFollowersCount();
@ -563,23 +632,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie // final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId()); // .equals(profileModel.getId());
final String favorite = Utils.dataBox.getFavorite(username);
binding.btnFollow.setOnClickListener(v -> { binding.btnFollow.setOnClickListener(v -> {
if (!isLoggedIn) {
if (favorite != null && v == binding.btnFollow) {
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(
username,
Long.parseLong(favorite.split("/")[1]),
username.replaceAll("^@", "")));
} else if (v == binding.btnFollow) {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
username,
System.currentTimeMillis(),
username.replaceAll("^@", "")));
}
fetchProfileDetails();
return;
}
if (profileModel.getFollowing() || profileModel.getRequested()) { if (profileModel.getFollowing() || profileModel.getRequested()) {
friendshipService.unfollow( friendshipService.unfollow(
userIdFromCookie, userIdFromCookie,
@ -589,7 +642,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) { public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unfollow success: " + result); Log.d(TAG, "Unfollow success: " + result);
fetchProfileDetails(); onRefresh();
} }
@Override @Override
@ -606,7 +659,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) { public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Follow success: " + result); Log.d(TAG, "Follow success: " + result);
fetchProfileDetails(); onRefresh();
} }
@Override @Override
@ -616,64 +669,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}); });
} }
}); });
binding.btnRestrict.setOnClickListener(v -> {
if (!isLoggedIn) return;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
friendshipService.toggleRestrict(
profileModel.getId(),
!profileModel.getRestricted(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoRestrictRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoRestrictRootResponse result) {
Log.d(TAG, action + " success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error while performing " + action, t);
}
});
});
binding.btnBlock.setOnClickListener(v -> {
if (!isLoggedIn) return;
if (profileModel.getBlocked()) {
friendshipService.unblock(
userIdFromCookie,
profileModel.getId(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unblock success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unblocking", t);
}
});
return;
}
friendshipService.block(
userIdFromCookie,
profileModel.getId(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Block success: " + result);
fetchProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error blocking", t);
}
});
});
binding.btnSaved.setOnClickListener(v -> { binding.btnSaved.setOnClickListener(v -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(),
profileModel.getId(), profileModel.getId(),
@ -692,6 +687,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED); PostItemType.TAGGED);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}); });
binding.btnDM.setOnClickListener(v -> {
new CreateThreadAction(cookie, profileModel.getId(), threadId -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername());
NavHostFragment.findNavController(this).navigate(action);
}).execute();
});
binding.mainProfileImage.setOnClickListener(v -> { binding.mainProfileImage.setOnClickListener(v -> {
if (storyModels == null || storyModels.length <= 0) { if (storyModels == null || storyModels.length <= 0) {
// show profile pic // show profile pic
@ -721,6 +722,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
}); });
binding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> {
// do not do anything if state matches the db, as listener is set before profile details are set
final String finalUsername = username.startsWith("@") ? username.substring(1) : username;
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER);
if ((isChecked && favorite != null) || (!isChecked && favorite == null)) {
return;
}
buttonView.setVisibility(View.GONE);
binding.favProgress.setVisibility(View.VISIBLE);
final String message;
if (isChecked) {
final DataBox.FavoriteModel model = new DataBox.FavoriteModel(
-1,
finalUsername,
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
);
Utils.dataBox.addOrUpdateFavorite(model);
binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
} else {
Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER);
message = getString(R.string.removed_from_favs);
binding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
binding.favProgress.setVisibility(View.GONE);
binding.favCb.setVisibility(View.VISIBLE);
});
} }
private void showProfilePicDialog() { private void showProfilePicDialog() {

View File

@ -29,12 +29,13 @@ public class AboutFragment extends BasePreferencesFragment {
final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context); final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context);
screen.addPreference(thirdPartyCategory); screen.addPreference(thirdPartyCategory);
thirdPartyCategory.setTitle(R.string.about_category_3pt); thirdPartyCategory.setTitle(R.string.about_category_3pt);
thirdPartyCategory.setSummary(R.string.about_category_3pt_summary); //thirdPartyCategory.setSummary(R.string.about_category_3pt_summary);
thirdPartyCategory.setIconSpaceReserved(false); thirdPartyCategory.setIconSpaceReserved(false);
// alphabetical order!!! // alphabetical order!!!
thirdPartyCategory.addPreference(getExoPlayerPreference()); thirdPartyCategory.addPreference(getExoPlayerPreference());
thirdPartyCategory.addPreference(getFrescoPreference()); thirdPartyCategory.addPreference(getFrescoPreference());
thirdPartyCategory.addPreference(getJsoupPreference()); thirdPartyCategory.addPreference(getJsoupPreference());
thirdPartyCategory.addPreference(getMDIPreference());
thirdPartyCategory.addPreference(getRetrofitPreference()); thirdPartyCategory.addPreference(getRetrofitPreference());
final PreferenceCategory licenseCategory = new PreferenceCategory(context); final PreferenceCategory licenseCategory = new PreferenceCategory(context);
@ -157,6 +158,22 @@ public class AboutFragment extends BasePreferencesFragment {
return preference; return preference;
} }
private Preference getMDIPreference() {
final Context context = getContext();
if (context == null) return null;
final Preference preference = new Preference(context);
preference.setTitle("Material Design Icons");
preference.setSummary("Copyright (C) 2014 Austin Andrews & Google LLC. Apache Version 2.0.");
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://materialdesignicons.com/"));
startActivity(intent);
return true;
});
return preference;
}
private Preference getLicensePreference() { private Preference getLicensePreference() {
final Context context = getContext(); final Context context = getContext();
if (context == null) return null; if (context == null) return null;

View File

@ -0,0 +1,107 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.CreateBackupDialogFragment;
import awais.instagrabber.dialogs.RestoreBackupDialogFragment;
public class BackupPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) {
return;
}
screen.addPreference(getCreatePreference(context));
screen.addPreference(getRestorePreference(context));
}
private Preference getCreatePreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.create_backup);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final FragmentManager fragmentManager = getParentFragmentManager();
final CreateBackupDialogFragment fragment = new CreateBackupDialogFragment(result -> {
final View view = getView();
if (view != null) {
Snackbar.make(view,
result ? R.string.dialog_export_success
: R.string.dialog_export_failed,
BaseTransientBottomBar.LENGTH_LONG)
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAction(R.string.ok, v -> {})
.show();
return;
}
Toast.makeText(context,
result ? R.string.dialog_export_success
: R.string.dialog_export_failed,
Toast.LENGTH_LONG)
.show();
});
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "createBackup")
.commit();
return true;
});
return preference;
}
private Preference getRestorePreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.restore_backup);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final FragmentManager fragmentManager = getParentFragmentManager();
final RestoreBackupDialogFragment fragment = new RestoreBackupDialogFragment(result -> {
final View view = getView();
if (view != null) {
Snackbar.make(view,
result ? R.string.dialog_import_success
: R.string.dialog_import_failed,
BaseTransientBottomBar.LENGTH_LONG)
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAction(R.string.ok, v -> {})
.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(final Snackbar transientBottomBar, final int event) {
recreateActivity(result);
}
})
.show();
return;
}
recreateActivity(result);
});
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "restoreBackup")
.commit();
return true;
});
return preference;
}
private void recreateActivity(final boolean result) {
if (!result) return;
final FragmentActivity activity = getActivity();
if (activity == null) return;
activity.recreate();
}
}

View File

@ -21,7 +21,7 @@ public abstract class BasePreferencesFragment extends PreferenceFragmentCompat i
@Override @Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final PreferenceManager preferenceManager = getPreferenceManager(); final PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName("settings"); preferenceManager.setSharedPreferencesName(Constants.SHARED_PREFERENCES_NAME);
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;

View File

@ -20,7 +20,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import java.util.ArrayList; import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -55,11 +55,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
accountCategory.setTitle(R.string.account); accountCategory.setTitle(R.string.account);
accountCategory.setIconSpaceReserved(false); accountCategory.setIconSpaceReserved(false);
screen.addPreference(accountCategory); screen.addPreference(accountCategory);
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies(); final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (isLoggedIn) { if (isLoggedIn) {
accountCategory.setSummary(R.string.account_hint); accountCategory.setSummary(R.string.account_hint);
accountCategory.addPreference(getAccountSwitcherPreference(cookie)); accountCategory.addPreference(getAccountSwitcherPreference(cookie));
accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout, preference -> { accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout_24, preference -> {
if (getContext() == null) return false; if (getContext() == null) return false;
CookieUtils.setupCookies("LOGOUT"); CookieUtils.setupCookies("LOGOUT");
shouldRecreate(); shouldRecreate();
@ -79,7 +79,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
} }
if (allCookies != null && allCookies.size() > 0) { if (allCookies != null && allCookies.size() > 0) {
accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_delete, preference -> { accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> {
if (getContext() == null) return false; if (getContext() == null) return false;
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(getContext())
.setTitle(R.string.logout) .setTitle(R.string.logout)
@ -96,37 +96,49 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
})); }));
} }
final PreferenceCategory generalCategory = new PreferenceCategory(context); // final PreferenceCategory generalCategory = new PreferenceCategory(context);
generalCategory.setTitle(R.string.pref_category_general); // generalCategory.setTitle(R.string.pref_category_general);
generalCategory.setIconSpaceReserved(false); // generalCategory.setIconSpaceReserved(false);
screen.addPreference(generalCategory); // screen.addPreference(generalCategory);
screen.addPreference(getDivider(context));
if (isLoggedIn) { if (isLoggedIn) {
generalCategory.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToNotificationsViewer(); final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToNotificationsViewer();
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);
return true; return true;
})); }));
} }
generalCategory.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getDivider(context));
screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment(); final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);
return true; return true;
})); }));
final Preference aboutPreference = getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference -> { screen.addPreference(getPreference(R.string.backup_and_restore, R.drawable.ic_settings_backup_restore_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment(); final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment();
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);
return true; return true;
}); }));
generalCategory.addPreference(aboutPreference);
screen.addPreference(getDivider(context)); screen.addPreference(getDivider(context));
screen.addPreference(getPreference(R.string.version,
final Preference versionPreference = getPreference(R.string.version, BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", -1, preference -> { -1,
FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true); preference -> {
return true; FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true);
}); return true;
screen.addPreference(versionPreference); }));
screen.addPreference(getDivider(context)); screen.addPreference(getDivider(context));
final Preference reminderPreference = getPreference(R.string.reminder, R.string.reminder_summary, R.drawable.ic_warning, null); final Preference reminderPreference = getPreference(R.string.reminder, R.string.reminder_summary, R.drawable.ic_warning, null);

View File

@ -180,9 +180,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
if (context == null) return null; if (context == null) return null;
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(path -> { .setInteractionListener(file -> {
settingsHelper.putString(FOLDER_PATH, path); settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
resultCallback.onResult(path); resultCallback.onResult(file.getAbsolutePath());
}) })
.show(getParentFragmentManager(), null)); .show(getParentFragmentManager(), null));
} }

View File

@ -0,0 +1,7 @@
package awais.instagrabber.models.enums;
public enum FavoriteType {
USER,
HASHTAG,
LOCATION
}

View File

@ -4,9 +4,8 @@ import java.io.Serializable;
public enum StoryViewerChoice implements Serializable { public enum StoryViewerChoice implements Serializable {
NONE(0), NONE(0),
STORIESIG(1), ALOINSTAGRAM(1),
ALOINSTAGRAM(2), INSTADP(2);
INSTADP(3);
private int value; private int value;

View File

@ -0,0 +1,19 @@
package awais.instagrabber.repositories;
import retrofit2.Call;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Path;
public interface TagsRepository {
@POST("/web/tags/follow/{tag}/")
Call<String> follow(@Header("User-Agent") String userAgent,
@Header("x-csrftoken") String csrfToken,
@Path("tag") String tag);
@POST("/web/tags/unfollow/{tag}/")
Call<String> unfollow(@Header("User-Agent") String userAgent,
@Header("x-csrftoken") String csrfToken,
@Path("tag") String tag);
}

View File

@ -9,6 +9,7 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
@ -115,18 +116,24 @@ public class ActivityCheckerService extends Service {
} }
private void showNotification(final String notificationString) { private void showNotification(final String notificationString) {
final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
.setAction(Constants.ACTION_SHOW_ACTIVITY)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID) final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS) .setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.drawable.ic_notif) .setSmallIcon(R.drawable.ic_notif)
.setAutoCancel(true) .setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_MIN) .setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentTitle(getString(R.string.action_notif)) .setContentTitle(getString(R.string.action_notif))
.setContentText(notificationString) .setContentText(notificationString)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT)) .setContentIntent(getPendingIntent())
.build(); .build();
notificationManager.notify(Constants.ACTIVITY_NOTIFICATION_ID, notification); notificationManager.notify(Constants.ACTIVITY_NOTIFICATION_ID, notification);
} }
@NonNull
private PendingIntent getPendingIntent() {
final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
.setAction(Constants.ACTION_SHOW_ACTIVITY)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
} }

View File

@ -82,4 +82,6 @@ public final class Constants {
public static final String ACTION_SHOW_ACTIVITY = "show_activity"; public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String PREF_DARK_THEME = "dark_theme"; public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme"; public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
public static final String SHARED_PREFERENCES_NAME = "settings";
} }

View File

@ -6,15 +6,18 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat; import androidx.core.util.ObjectsCompat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
@ -24,12 +27,9 @@ public final class DataBox extends SQLiteOpenHelper {
private static DataBox sInstance; private static DataBox sInstance;
private final static int VERSION = 2; private final static int VERSION = 3;
private final static String TABLE_COOKIES = "cookies"; private final static String TABLE_COOKIES = "cookies";
private final static String TABLE_FAVORITES = "favorites"; private final static String TABLE_FAVORITES = "favorites";
private final static String KEY_DATE_ADDED = "date_added";
private final static String KEY_QUERY_TEXT = "query_text";
private final static String KEY_QUERY_DISPLAY = "query_display";
private final static String KEY_ID = "id"; private final static String KEY_ID = "id";
private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME;
@ -38,7 +38,12 @@ public final class DataBox extends SQLiteOpenHelper {
private final static String KEY_FULL_NAME = "full_name"; private final static String KEY_FULL_NAME = "full_name";
private final static String KEY_PROFILE_PIC = "profile_pic"; private final static String KEY_PROFILE_PIC = "profile_pic";
private final Context c; private final static String FAV_COL_ID = "id";
private final static String FAV_COL_QUERY = "query_text";
private final static String FAV_COL_TYPE = "type";
private final static String FAV_COL_DISPLAY_NAME = "display_name";
private final static String FAV_COL_PIC_URL = "pic_url";
private final static String FAV_COL_DATE_ADDED = "date_added";
public static synchronized DataBox getInstance(final Context context) { public static synchronized DataBox getInstance(final Context context) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
@ -47,7 +52,6 @@ public final class DataBox extends SQLiteOpenHelper {
private DataBox(@Nullable final Context context) { private DataBox(@Nullable final Context context) {
super(context, "cookiebox.db", null, VERSION); super(context, "cookiebox.db", null, VERSION);
c = context;
} }
@Override @Override
@ -60,42 +64,120 @@ public final class DataBox extends SQLiteOpenHelper {
+ KEY_COOKIE + " TEXT," + KEY_COOKIE + " TEXT,"
+ KEY_FULL_NAME + " TEXT," + KEY_FULL_NAME + " TEXT,"
+ KEY_PROFILE_PIC + " TEXT)"); + KEY_PROFILE_PIC + " TEXT)");
db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); // db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)");
db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " ("
+ FAV_COL_ID + " INTEGER PRIMARY KEY,"
+ FAV_COL_QUERY + " TEXT,"
+ FAV_COL_TYPE + " TEXT,"
+ FAV_COL_DISPLAY_NAME + " TEXT,"
+ FAV_COL_PIC_URL + " TEXT,"
+ FAV_COL_DATE_ADDED + " INTEGER)");
Log.i(TAG, "Tables created!"); Log.i(TAG, "Tables created!");
} }
@Override @Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion)); Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion));
if (oldVersion == 1) { // switch without break, so that all migrations from a previous version to new are run
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); switch (oldVersion) {
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); case 1:
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
case 2:
final List<FavoriteModel> oldFavorites = backupOldFavorites(db);
// recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions)
db.execSQL("DROP TABLE " + TABLE_FAVORITES);
db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " ("
+ FAV_COL_ID + " INTEGER PRIMARY KEY,"
+ FAV_COL_QUERY + " TEXT,"
+ FAV_COL_TYPE + " TEXT,"
+ FAV_COL_DISPLAY_NAME + " TEXT,"
+ FAV_COL_PIC_URL + " TEXT,"
+ FAV_COL_DATE_ADDED + " INTEGER)");
// add the old favorites back
for (final FavoriteModel oldFavorite : oldFavorites) {
addOrUpdateFavorite(db, oldFavorite);
}
} }
Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion));
} }
public final void addFavorite(@NonNull final FavoriteModel favoriteModel) { @NonNull
final String query = favoriteModel.getQuery(); private List<FavoriteModel> backupOldFavorites(@NonNull final SQLiteDatabase db) {
final String display = favoriteModel.getDisplayName(); // check if old favorites table had the column query_display
final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display");
Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists);
final List<FavoriteModel> oldModels = new ArrayList<>();
final String sql = "SELECT "
+ "query_text,"
+ "date_added"
+ (queryDisplayExists ? ",query_display" : "")
+ " FROM " + TABLE_FAVORITES;
try (final Cursor cursor = db.rawQuery(sql, null)) {
if (cursor != null && cursor.moveToFirst()) {
do {
try {
final String queryText = cursor.getString(cursor.getColumnIndex("query_text"));
final Pair<FavoriteType, String> favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText);
if (favoriteTypeQueryPair == null) continue;
final FavoriteType type = favoriteTypeQueryPair.first;
final String query = favoriteTypeQueryPair.second;
oldModels.add(new FavoriteModel(
-1,
query,
type,
queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display"))
: null,
null,
new Date(cursor.getLong(cursor.getColumnIndex("date_added")))
));
} catch (Exception e) {
Log.e(TAG, "onUpgrade", e);
}
} while (cursor.moveToNext());
}
} catch (Exception e) {
Log.e(TAG, "onUpgrade", e);
}
Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels);
return oldModels;
}
public boolean checkColumnExists(@NonNull final SQLiteDatabase db,
@NonNull final String tableName,
@NonNull final String columnName) {
boolean exists = false;
try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) {
if (cursor.moveToFirst()) {
do {
final String currentColumn = cursor.getString(cursor.getColumnIndex("name"));
if (currentColumn.equals(columnName)) {
exists = true;
}
} while (cursor.moveToNext());
}
} catch (Exception ex) {
Log.e(TAG, "checkColumnExists", ex);
}
return exists;
}
public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) {
final String query = model.getQuery();
if (!TextUtils.isEmpty(query)) { if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) { try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction(); db.beginTransaction();
try { try {
final ContentValues values = new ContentValues(); addOrUpdateFavorite(db, model);
values.put(KEY_DATE_ADDED, favoriteModel.getDate());
values.put(KEY_QUERY_TEXT, query);
values.put(KEY_QUERY_DISPLAY, display);
final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{query});
if (rows != 1)
db.insertOrThrow(TABLE_FAVORITES, null, values);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite"); logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error adding/updating favorite", e);
}
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
@ -103,23 +185,46 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) { private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
final String query = favoriteModel.getQuery(); final ContentValues values = new ContentValues();
values.put(FAV_COL_QUERY, model.getQuery());
values.put(FAV_COL_TYPE, model.getType().toString());
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
values.put(FAV_COL_PIC_URL, model.getPicUrl());
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
int rows;
if (model.getId() >= 1) {
rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())});
} else {
rows = db.update(TABLE_FAVORITES,
values,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{model.getQuery(), model.getType().toString()});
}
if (rows != 1) {
db.insertOrThrow(TABLE_FAVORITES, null, values);
}
}
public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
if (!TextUtils.isEmpty(query)) { if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) { try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction(); db.beginTransaction();
try { try {
final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", final int rowsDeleted = db.delete(TABLE_FAVORITES,
new String[]{query, Long.toString(favoriteModel.getDate())}); FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", if (rowsDeleted > 0) db.setTransactionSuccessful();
new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())});
if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
@ -127,76 +232,87 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
@Nullable @NonNull
public final ArrayList<FavoriteModel> getAllFavorites() { public final List<FavoriteModel> getAllFavorites() {
ArrayList<FavoriteModel> favorites = null; final List<FavoriteModel> favorites = new ArrayList<>();
FavoriteModel tempFav;
final SQLiteDatabase db = getWritableDatabase(); final SQLiteDatabase db = getWritableDatabase();
try (final Cursor cursor = db.rawQuery("SELECT "
try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) { + FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES,
null)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
db.beginTransaction(); db.beginTransaction();
favorites = new ArrayList<>(); FavoriteModel tempFav;
do { do {
FavoriteType type = null;
try {
type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
tempFav = new FavoriteModel( tempFav = new FavoriteModel(
(cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor.getString(0).contains("/")) cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
? cursor.getString(0) cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
: "@" + cursor.getString(0), // query text type,
cursor.getLong(1), // date added cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(2) == null ? (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
.getString(0).contains("/")) new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
? cursor.getString(0)
: "@" + cursor.getString(0) : cursor.getString(2) // display
); );
if (cursor.getString(2) == null) {
try {
final ContentValues values = new ContentValues();
values.put(KEY_DATE_ADDED, tempFav.getDate());
values.put(KEY_QUERY_TEXT, tempFav.getQuery());
values.put(KEY_QUERY_DISPLAY, tempFav.getDisplayName());
final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{tempFav.getQuery()});
if (rows != 1)
db.insertOrThrow(TABLE_FAVORITES, null, values);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
favorites.add(tempFav); favorites.add(tempFav);
} while (cursor.moveToNext()); } while (cursor.moveToNext());
db.endTransaction(); db.endTransaction();
} }
} catch (final Exception x) { } catch (final Exception e) {
Log.e("austin_debug", "", x); Log.e(TAG, "", e);
try {
db.execSQL("ALTER TABLE favorites ADD query_display TEXT");
Toast.makeText(c, "DB has migrated, launch quick access again.", Toast.LENGTH_SHORT).show();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "migrate");
Toast.makeText(c, "DB migration failed, contact maintainer.", Toast.LENGTH_SHORT).show();
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} }
return favorites; return favorites;
} }
public final String getFavorite(@NonNull final String query) { @Nullable
public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
try (final SQLiteDatabase db = getReadableDatabase(); try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE " final Cursor cursor = db.rawQuery("SELECT "
+ KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) { + FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES
+ " WHERE " + FAV_COL_QUERY + "='" + query + "'"
+ " AND " + FAV_COL_TYPE + "='" + type.toString() + "'",
null)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0) + "/" + String.valueOf(cursor.getLong(1)); FavoriteType favoriteType = null;
try {
favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
return new FavoriteModel(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
favoriteType,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
} }
} }
return null; return null;
} }
public final void addOrUpdateUser(@NonNull final DataBox.CookieModel cookieModel) {
addOrUpdateUser(
cookieModel.getUid(),
cookieModel.getUsername(),
cookieModel.getCookie(),
cookieModel.getFullName(),
cookieModel.getProfilePic()
);
}
public final void addOrUpdateUser(final String uid, public final void addOrUpdateUser(final String uid,
final String username, final String username,
final String cookie, final String cookie,
@ -261,15 +377,6 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
public final int getCookieCount() {
int cookieCount = 0;
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT * FROM cookies", null)) {
if (cursor != null) cookieCount = cursor.getCount();
}
return cookieCount;
}
@Nullable @Nullable
public final CookieModel getCookie(final String uid) { public final CookieModel getCookie(final String uid) {
CookieModel cookie = null; CookieModel cookie = null;
@ -297,10 +404,9 @@ public final class DataBox extends SQLiteOpenHelper {
return cookie; return cookie;
} }
@Nullable @NonNull
public final ArrayList<CookieModel> getAllCookies() { public final List<CookieModel> getAllCookies() {
ArrayList<CookieModel> cookies = null; final List<CookieModel> cookies = new ArrayList<>();
try (final SQLiteDatabase db = getReadableDatabase(); try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery( final Cursor cursor = db.rawQuery(
"SELECT " "SELECT "
@ -312,7 +418,6 @@ public final class DataBox extends SQLiteOpenHelper {
+ " FROM " + TABLE_COOKIES, null) + " FROM " + TABLE_COOKIES, null)
) { ) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
cookies = new ArrayList<>();
do { do {
cookies.add(new CookieModel( cookies.add(new CookieModel(
cursor.getString(cursor.getColumnIndex(KEY_UID)), cursor.getString(cursor.getColumnIndex(KEY_UID)),
@ -324,7 +429,6 @@ public final class DataBox extends SQLiteOpenHelper {
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} }
return cookies; return cookies;
} }
@ -376,6 +480,12 @@ public final class DataBox extends SQLiteOpenHelper {
this.selected = selected; this.selected = selected;
} }
public boolean isValid() {
return !TextUtils.isEmpty(uid)
&& !TextUtils.isEmpty(username)
&& !TextUtils.isEmpty(cookie);
}
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
if (this == o) return true; if (this == o) return true;
@ -394,36 +504,92 @@ public final class DataBox extends SQLiteOpenHelper {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return username; return "CookieModel{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", cookie='" + cookie + '\'' +
", fullName='" + fullName + '\'' +
", profilePic='" + profilePic + '\'' +
", selected=" + selected +
'}';
} }
} }
public static class FavoriteModel { public static class FavoriteModel {
private final String query, displayName; private final int id;
private final long date; private final String query;
private final FavoriteType type;
private final String displayName;
private final String picUrl;
private final Date dateAdded;
public FavoriteModel(final String query, final long date, final String displayName) { public FavoriteModel(final int id,
final String query,
final FavoriteType type,
final String displayName,
final String picUrl,
final Date dateAdded) {
this.id = id;
this.query = query; this.query = query;
this.date = date; this.type = type;
this.displayName = displayName; this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
} }
public String getQuery() { public String getQuery() {
return query; return query;
} }
public FavoriteType getType() {
return type;
}
public String getDisplayName() { public String getDisplayName() {
return displayName; return displayName;
} }
public long getDate() { public String getPicUrl() {
return date; return picUrl;
}
public Date getDateAdded() {
return dateAdded;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final FavoriteModel that = (FavoriteModel) o;
return id == that.id &&
ObjectsCompat.equals(query, that.query) &&
type == that.type &&
ObjectsCompat.equals(displayName, that.displayName) &&
ObjectsCompat.equals(picUrl, that.picUrl) &&
ObjectsCompat.equals(dateAdded, that.dateAdded);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded);
} }
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return query; return "FavoriteModel{" +
"id=" + id +
", query='" + query + '\'' +
", type=" + type +
", displayName='" + displayName + '\'' +
", picUrl='" + picUrl + '\'' +
", dateAdded=" + dateAdded +
'}';
} }
} }
} }

View File

@ -3,20 +3,26 @@ package awais.instagrabber.utils;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.FileObserver; import android.os.FileObserver;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,22 +30,27 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.SimpleAdapter; import awais.instagrabber.adapters.DirectoryFilesAdapter;
import awais.instagrabber.databinding.LayoutDirectoryChooserBinding;
import awais.instagrabber.viewmodels.FileListViewModel;
public final class DirectoryChooser extends DialogFragment { public final class DirectoryChooser extends DialogFragment {
private static final String TAG = "DirectoryChooser";
public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY"; public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY";
private static final File sdcardPathFile = Environment.getExternalStorageDirectory(); private static final File sdcardPathFile = Environment.getExternalStorageDirectory();
private static final String sdcardPath = sdcardPathFile.getPath(); private static final String sdcardPath = sdcardPathFile.getPath();
private final List<String> fileNames = new ArrayList<>();
private Context context; private Context context;
private View btnConfirm, btnNavUp, btnCancel; private LayoutDirectoryChooserBinding binding;
private FileObserver fileObserver;
private File selectedDir; private File selectedDir;
private String initialDirectory; private String initialDirectory;
private TextView tvSelectedFolder;
private FileObserver fileObserver;
private SimpleAdapter<String> listDirectoriesAdapter;
private OnFragmentInteractionListener interactionListener; private OnFragmentInteractionListener interactionListener;
private boolean showZaAiConfigFiles = false; private boolean showBackupFiles = false;
private View.OnClickListener navigationOnClickListener;
private FileListViewModel fileListViewModel;
private OnCancelListener onCancelListener;
public DirectoryChooser() { public DirectoryChooser() {
super(); super();
@ -51,8 +62,8 @@ public final class DirectoryChooser extends DialogFragment {
return this; return this;
} }
public DirectoryChooser setShowZaAiConfigFiles(final boolean showZaAiConfigFiles) { public DirectoryChooser setShowBackupFiles(final boolean showBackupFiles) {
this.showZaAiConfigFiles = showZaAiConfigFiles; this.showBackupFiles = showBackupFiles;
return this; return this;
} }
@ -74,60 +85,71 @@ public final class DirectoryChooser extends DialogFragment {
@NonNull @NonNull
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = LayoutDirectoryChooserBinding.inflate(inflater, container, false);
init(container);
return binding.getRoot();
}
private void init(final ViewGroup container) {
Context context = this.context; Context context = this.context;
if (context == null) context = getContext(); if (context == null) context = getContext();
if (context == null) context = getActivity(); if (context == null) context = getActivity();
if (context == null) context = inflater.getContext(); if (context == null) return;
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
final View view = inflater.inflate(R.layout.layout_directory_chooser, container, false); final String text = "Storage permissions denied!";
if (container == null) {
btnNavUp = view.findViewById(R.id.btnNavUp); Toast.makeText(context, text, Toast.LENGTH_LONG).show();
btnCancel = view.findViewById(R.id.btnCancel); } else {
btnConfirm = view.findViewById(R.id.btnConfirm); Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
tvSelectedFolder = view.findViewById(R.id.txtvSelectedFolder); }
dismiss();
}
final View.OnClickListener clickListener = v -> { final View.OnClickListener clickListener = v -> {
final Object tag; if (v == binding.btnConfirm) {
if (v instanceof TextView && (tag = v.getTag()) instanceof CharSequence) {
final File file = new File(selectedDir, tag.toString());
if (file.isDirectory())
changeDirectory(file);
else if (showZaAiConfigFiles && file.isFile()) {
if (interactionListener != null && file.canRead())
interactionListener.onSelectDirectory(file.getAbsolutePath());
dismiss();
}
} else if (v == btnNavUp) {
final File parent;
if (selectedDir != null && (parent = selectedDir.getParentFile()) != null)
changeDirectory(parent);
} else if (v == btnConfirm) {
if (interactionListener != null && isValidFile(selectedDir)) if (interactionListener != null && isValidFile(selectedDir))
interactionListener.onSelectDirectory(selectedDir.getAbsolutePath()); interactionListener.onSelectDirectory(selectedDir);
dismiss(); dismiss();
} else if (v == btnCancel) { } else if (v == binding.btnCancel) {
if (onCancelListener != null) {
onCancelListener.onCancel();
}
dismiss(); dismiss();
} }
}; };
btnNavUp.setOnClickListener(clickListener); navigationOnClickListener = v -> {
btnCancel.setOnClickListener(clickListener); final File parent;
btnConfirm.setOnClickListener(clickListener); if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) {
changeDirectory(parent);
listDirectoriesAdapter = new SimpleAdapter<>(context, fileNames, clickListener); }
};
final RecyclerView directoriesList = view.findViewById(R.id.directoryList); binding.toolbar.setNavigationOnClickListener(navigationOnClickListener);
directoriesList.setLayoutManager(new LinearLayoutManager(context)); binding.toolbar.setSubtitle(showBackupFiles ? R.string.select_backup_file : R.string.select_folder);
directoriesList.setAdapter(listDirectoriesAdapter); binding.btnCancel.setOnClickListener(clickListener);
// no need to show confirm for file picker
binding.btnConfirm.setVisibility(showBackupFiles ? View.GONE : View.VISIBLE);
if (!showBackupFiles) {
binding.btnConfirm.setOnClickListener(clickListener);
}
fileListViewModel = new ViewModelProvider(this).get(FileListViewModel.class);
final DirectoryFilesAdapter listDirectoriesAdapter = new DirectoryFilesAdapter(file -> {
if (file.isDirectory()) {
changeDirectory(file);
return;
}
if (showBackupFiles && file.isFile()) {
if (interactionListener != null && file.canRead()) {
interactionListener.onSelectDirectory(file);
}
dismiss();
}
});
fileListViewModel.getList().observe(this, listDirectoriesAdapter::submitList);
binding.directoryList.setLayoutManager(new LinearLayoutManager(context));
binding.directoryList.setAdapter(listDirectoriesAdapter);
final File initDir = new File(initialDirectory); final File initDir = new File(initialDirectory);
final File initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory(); final File initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory();
changeDirectory(initialDir); changeDirectory(initialDir);
return view;
} }
@Override @Override
@ -153,10 +175,14 @@ public final class DirectoryChooser extends DialogFragment {
public void onBackPressed() { public void onBackPressed() {
if (selectedDir != null) { if (selectedDir != null) {
final String absolutePath = selectedDir.getAbsolutePath(); final String absolutePath = selectedDir.getAbsolutePath();
if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) {
if (onCancelListener != null) {
onCancelListener.onCancel();
}
dismiss(); dismiss();
else } else {
changeDirectory(selectedDir.getParentFile()); changeDirectory(selectedDir.getParentFile());
}
} }
} }
}; };
@ -189,21 +215,28 @@ public final class DirectoryChooser extends DialogFragment {
private void changeDirectory(final File dir) { private void changeDirectory(final File dir) {
if (dir != null && dir.isDirectory()) { if (dir != null && dir.isDirectory()) {
final String path = dir.getAbsolutePath(); final String path = dir.getAbsolutePath();
binding.toolbar.setTitle(path);
final File[] contents = dir.listFiles(); final File[] contents = dir.listFiles();
if (contents != null) { if (contents != null) {
fileNames.clear(); final List<File> fileNames = new ArrayList<>();
for (final File f : contents) { for (final File f : contents) {
final String name = f.getName(); final String name = f.getName();
if (f.isDirectory() || showZaAiConfigFiles && f.isFile() && name.toLowerCase().endsWith(".zaai")) final String nameLowerCase = name.toLowerCase();
fileNames.add(name); final boolean isBackupFile = nameLowerCase.endsWith(".zaai") || nameLowerCase.endsWith(".backup");
if (f.isDirectory() || (showBackupFiles && f.isFile() && isBackupFile))
fileNames.add(f);
} }
Collections.sort(fileNames, (o1, o2) -> {
Collections.sort(fileNames); if ((o1.isDirectory() && o2.isDirectory())
|| (o1.isFile() && o2.isFile())) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
if (o1.isDirectory()) return -1;
if (o2.isDirectory()) return 1;
return 0;
});
fileListViewModel.getList().postValue(fileNames);
selectedDir = dir; selectedDir = dir;
tvSelectedFolder.setText(path);
listDirectoriesAdapter.notifyDataSetChanged();
fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) {
private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir); private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir);
@ -222,15 +255,15 @@ public final class DirectoryChooser extends DialogFragment {
if (selectedDir != null) { if (selectedDir != null) {
final String path = selectedDir.getAbsolutePath(); final String path = selectedDir.getAbsolutePath();
toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile); toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile);
btnConfirm.setEnabled(isValidFile(selectedDir)); binding.btnConfirm.setEnabled(isValidFile(selectedDir));
} }
} }
private void toggleUpButton(final boolean enable) { private void toggleUpButton(final boolean enable) {
if (btnNavUp != null) { binding.toolbar.setNavigationOnClickListener(enable ? navigationOnClickListener : null);
btnNavUp.setEnabled(enable); final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
btnNavUp.setAlpha(enable ? 1f : 0.617f); if (navigationIcon == null) return;
} navigationIcon.setAlpha(enable ? 255 : (int) (255 * 0.617));
} }
private boolean isValidFile(final File file) { private boolean isValidFile(final File file) {
@ -242,7 +275,17 @@ public final class DirectoryChooser extends DialogFragment {
return this; return this;
} }
public void setOnCancelListener(final OnCancelListener onCancelListener) {
if (onCancelListener != null) {
this.onCancelListener = onCancelListener;
}
}
public interface OnCancelListener {
void onCancel();
}
public interface OnFragmentInteractionListener { public interface OnFragmentInteractionListener {
void onSelectDirectory(final String path); void onSelectDirectory(final File file);
} }
} }

View File

@ -0,0 +1,76 @@
package awais.instagrabber.utils;
import android.os.Build;
import android.os.Environment;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class DirectoryUtils {
private static final Pattern DIR_SEPORATOR = Pattern.compile("/");
/**
* From: https://stackoverflow.com/a/18871043/1436766
*
* Returns all available SD-Cards in the system (include emulated)
* <p>
* Warning: Hack! Based on Android source code of version 4.3 (API 18)
* Because there is no standard way to get it.
* TODO: Test on future Android versions 4.4+
*
* @return paths to all available SD-Cards in the system (include emulated)
*/
public static Set<String> getStorageDirectories() {
// Final set of paths
final Set<String> rv = new HashSet<>();
// Primary physical SD-CARD (not emulated)
final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
// All Secondary SD-CARDs (all exclude primary) separated by ":"
final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
// Primary emulated SD-CARD
final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
// Device has physical external storage; use plain paths.
if (TextUtils.isEmpty(rawExternalStorage)) {
// EXTERNAL_STORAGE undefined; falling back to default.
rv.add("/storage/sdcard0");
} else {
rv.add(rawExternalStorage);
}
} else {
// Device has emulated storage; external storage paths should have
// userId burned into them.
final String rawUserId;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
rawUserId = "";
} else {
final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
final String[] folders = DIR_SEPORATOR.split(path);
final String lastFolder = folders[folders.length - 1];
boolean isDigit = false;
try {
Integer.valueOf(lastFolder);
isDigit = true;
} catch (NumberFormatException ignored) {
}
rawUserId = isDigit ? lastFolder : "";
}
// /storage/emulated/0[1,2,...]
if (TextUtils.isEmpty(rawUserId)) {
rv.add(rawEmulatedStorageTarget);
} else {
rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
}
}
// Add all secondary storages
if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
// All Secondary SD-CARDs splited into array
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
Collections.addAll(rv, rawSecondaryStorages);
}
return rv;
}
}

View File

@ -37,7 +37,9 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class DownloadUtils { public final class DownloadUtils {
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
public static void batchDownload(@NonNull final Context context, @Nullable String username, final DownloadMethod method, public static void batchDownload(@NonNull final Context context,
@Nullable String username,
final DownloadMethod method,
final List<? extends BasePostModel> itemsToDownload) { final List<? extends BasePostModel> itemsToDownload) {
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context); if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);

View File

@ -1,40 +1,40 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.content.Context; import android.content.Context;
import android.text.InputFilter; import android.content.SharedPreferences;
import android.text.InputType;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatEditText;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import javax.crypto.Cipher; import java.util.Map;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awaisomereport.LogCollector.LogFile; import awaisomereport.LogCollector.LogFile;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public final class ExportImportUtils { public final class ExportImportUtils {
private static final String TAG = "ExportImportUtils";
public static final int FLAG_COOKIES = 1; public static final int FLAG_COOKIES = 1;
public static final int FLAG_FAVORITES = 1 << 1; public static final int FLAG_FAVORITES = 1 << 1;
public static final int FLAG_SETTINGS = 1 << 2; public static final int FLAG_SETTINGS = 1 << 2;
@ -42,292 +42,289 @@ public final class ExportImportUtils {
@IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true) @IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true)
@interface ExportImportFlags {} @interface ExportImportFlags {}
public static void Export(@Nullable final String password, @ExportImportFlags final int flags, @NonNull final File filePath, public static void exportData(@Nullable final String password,
final FetchListener<Boolean> fetchListener) { @ExportImportFlags final int flags,
final String exportString = ExportImportUtils.getExportString(flags); @NonNull final File filePath,
if (!TextUtils.isEmpty(exportString)) { final FetchListener<Boolean> fetchListener,
final boolean isPass = !TextUtils.isEmpty(password); @NonNull final Context context) {
byte[] exportBytes = null; final String exportString = getExportString(flags, context);
if (TextUtils.isEmpty(exportString)) return;
if (isPass) { final boolean isPass = !TextUtils.isEmpty(password);
final byte[] passwordBytes = password.getBytes(); byte[] exportBytes = null;
final byte[] bytes = new byte[32]; if (isPass) {
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
try { System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
exportBytes = PasswordUtils.enc(exportString, bytes); try {
} catch (final Exception e) { exportBytes = PasswordUtils.enc(exportString, bytes);
if (fetchListener != null) fetchListener.onResult(false); } catch (final Exception e) {
if (logCollector != null) if (fetchListener != null) fetchListener.onResult(false);
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); if (logCollector != null)
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
} if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
} }
} else {
if (exportBytes != null && exportBytes.length > 1) { exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
} }
if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
} }
public static void Import(@NonNull final Context context, @ExportImportFlags final int flags, @NonNull final File filePath, public static void importData(@NonNull final Context context,
final FetchListener<Boolean> fetchListener) { @ExportImportFlags final int flags,
try (final FileInputStream fis = new FileInputStream(filePath)) { @NonNull final File file,
final String password,
final FetchListener<Boolean> fetchListener) throws IncorrectPasswordException {
try (final FileInputStream fis = new FileInputStream(file)) {
final int configType = fis.read(); final int configType = fis.read();
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
int c; int c;
while ((c = fis.read()) != -1) { while ((c = fis.read()) != -1) {
builder.append((char) c); builder.append((char) c);
} }
if (configType == 'A') { if (configType == 'A') {
// password // password
final AppCompatEditText editText = new AppCompatEditText(context); if (TextUtils.isEmpty(password)) return;
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(32)}); try {
editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); final byte[] passwordBytes = password.getBytes();
new AlertDialog.Builder(context).setView(editText).setTitle(R.string.password) final byte[] bytes = new byte[32];
.setPositiveButton(R.string.confirm, (dialog, which) -> { System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
final CharSequence text = editText.getText(); importJson(new String(PasswordUtils.dec(builder.toString(), bytes)),
if (!TextUtils.isEmpty(text)) { flags,
try { fetchListener);
final byte[] passwordBytes = text.toString().getBytes(); } catch (final IncorrectPasswordException e) {
final byte[] bytes = new byte[32]; throw e;
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); } catch (final Exception e) {
saveToSettings(new String(PasswordUtils.dec(builder.toString(), bytes)), flags, if (fetchListener != null) fetchListener.onResult(false);
fetchListener); if (logCollector != null)
} catch (final Exception e) { logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
if (fetchListener != null) fetchListener.onResult(false); if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e);
if (logCollector != null) }
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
} else
Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show();
}).show();
} else if (configType == 'Z') { } else if (configType == 'Z') {
saveToSettings(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
flags, fetchListener); flags,
fetchListener);
} else { } else {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, "File is corrupted!", Toast.LENGTH_LONG).show();
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
} }
} catch (IncorrectPasswordException e) {
// separately handle incorrect password
throw e;
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
} }
private static void saveToSettings(final String json, @ExportImportFlags final int flags, final FetchListener<Boolean> fetchListener) { private static void importJson(@NonNull final String json,
@ExportImportFlags final int flags,
final FetchListener<Boolean> fetchListener) {
try { try {
final JSONObject jsonObject = new JSONObject(json); final JSONObject jsonObject = new JSONObject(json);
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) { if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) {
final JSONObject objSettings = jsonObject.getJSONObject("settings"); importSettings(jsonObject);
final Iterator<String> keys = objSettings.keys();
while (keys.hasNext()) {
final String key = keys.next();
final Object val = objSettings.opt(key);
if (val instanceof String) {
settingsHelper.putString(key, (String) val);
} else if (val instanceof Integer) {
settingsHelper.putInteger(key, (int) val);
} else if (val instanceof Boolean) {
settingsHelper.putBoolean(key, (boolean) val);
}
}
} }
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) { if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) {
final JSONArray cookies = jsonObject.getJSONArray("cookies"); importAccounts(jsonObject);
final int cookiesLen = cookies.length();
for (int i = 0; i < cookiesLen; ++i) {
final JSONObject cookieObject = cookies.getJSONObject(i);
// final DataBox.CookieModel cookieModel = new DataBox.CookieModel(cookieObject.getString("i"),
// cookieObject.getString("u"),
// cookieObject.getString("c"),
// fullName,
// profilePic);
// Utils.dataBox.addOrUpdateUser(cookieModel.getUid(), cookieModel.getUserInfo(), cookieModel.getCookie());
}
} }
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) { if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) {
final JSONArray favs = jsonObject.getJSONArray("favs"); importFavorites(jsonObject);
final int favsLen = favs.length();
for (int i = 0; i < favsLen; ++i) {
final JSONObject favsObject = favs.getJSONObject(i);
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"),
favsObject.getLong("d"),
favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q")));
}
} }
if (fetchListener != null) fetchListener.onResult(true); if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings"); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
} }
private static void importFavorites(final JSONObject jsonObject) throws JSONException {
final JSONArray favs = jsonObject.getJSONArray("favs");
for (int i = 0; i < favs.length(); i++) {
final JSONObject favsObject = favs.getJSONObject(i);
final String queryText = favsObject.optString("q");
if (TextUtils.isEmpty(queryText)) continue;
final Pair<FavoriteType, String> favoriteTypeQueryPair;
String query = null;
FavoriteType favoriteType = null;
if (queryText.contains("@")
|| queryText.contains("#")
|| queryText.contains("/")) {
favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText);
if (favoriteTypeQueryPair != null) {
query = favoriteTypeQueryPair.second;
favoriteType = favoriteTypeQueryPair.first;
}
} else {
query = queryText;
favoriteType = FavoriteType.valueOf(favsObject.optString("type"));
}
if (query == null || favoriteType == null) {
continue;
}
final DataBox.FavoriteModel favoriteModel = new DataBox.FavoriteModel(
-1,
query,
favoriteType,
favsObject.optString("s"),
favoriteType == FavoriteType.HASHTAG ? null
: favsObject.optString("pic_url"),
new Date(favsObject.getLong("d")));
// Log.d(TAG, "importJson: favoriteModel: " + favoriteModel);
Utils.dataBox.addOrUpdateFavorite(favoriteModel);
}
}
private static void importAccounts(final JSONObject jsonObject) throws JSONException {
final JSONArray cookies = jsonObject.getJSONArray("cookies");
for (int i = 0; i < cookies.length(); i++) {
final JSONObject cookieObject = cookies.getJSONObject(i);
final DataBox.CookieModel cookieModel = new DataBox.CookieModel(
cookieObject.optString("i"),
cookieObject.optString("u"),
cookieObject.optString("c"),
cookieObject.optString("full_name"),
cookieObject.optString("profile_pic")
);
if (!cookieModel.isValid()) continue;
// Log.d(TAG, "importJson: cookieModel: " + cookieModel);
Utils.dataBox.addOrUpdateUser(cookieModel);
}
}
private static void importSettings(final JSONObject jsonObject) throws JSONException {
final JSONObject objSettings = jsonObject.getJSONObject("settings");
final Iterator<String> keys = objSettings.keys();
while (keys.hasNext()) {
final String key = keys.next();
final Object val = objSettings.opt(key);
// Log.d(TAG, "importJson: key: " + key + ", val: " + val);
if (val instanceof String) {
settingsHelper.putString(key, (String) val);
} else if (val instanceof Integer) {
settingsHelper.putInteger(key, (int) val);
} else if (val instanceof Boolean) {
settingsHelper.putBoolean(key, (boolean) val);
}
}
}
public static boolean isEncrypted(final File file) {
try (final FileInputStream fis = new FileInputStream(file)) {
final int configType = fis.read();
if (configType == 'A') {
return true;
}
} catch (final Exception e) {
Log.e(TAG, "isEncrypted", e);
}
return false;
}
@Nullable @Nullable
private static String getExportString(@ExportImportFlags final int flags) { private static String getExportString(@ExportImportFlags final int flags,
@NonNull final Context context) {
String result = null; String result = null;
try { try {
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
String str;
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
str = getSettings(); jsonObject.put("settings", getSettings(context));
if (str != null) jsonObject.put("settings", new JSONObject(str));
} }
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) { if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
str = getCookies(); jsonObject.put("cookies", getCookies());
if (str != null) jsonObject.put("cookies", new JSONArray(str));
} }
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) { if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
str = getFavorites(); jsonObject.put("favs", getFavorites());
if (str != null) jsonObject.put("favs", new JSONArray(str));
} }
result = jsonObject.toString(); result = jsonObject.toString();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return result; return result;
} }
@Nullable @NonNull
private static String getSettings() { private static JSONObject getSettings(@NonNull final Context context) {
String result = null; final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final Map<String, ?> allPrefs = sharedPreferences.getAll();
if (allPrefs == null) {
return new JSONObject();
}
try {
final JSONObject jsonObject = new JSONObject(allPrefs);
jsonObject.remove(Constants.COOKIE);
jsonObject.remove(Constants.DEVICE_UUID);
jsonObject.remove(Constants.PREV_INSTALL_VERSION);
return jsonObject;
} catch (Exception e) {
Log.e(TAG, "Error exporting settings", e);
}
return new JSONObject();
}
if (settingsHelper != null) { @NonNull
try { private static JSONArray getFavorites() {
final JSONObject json = new JSONObject(); if (Utils.dataBox == null) return new JSONArray();
json.put(Constants.APP_THEME, settingsHelper.getString(Constants.APP_THEME)); try {
json.put(Constants.APP_LANGUAGE, settingsHelper.getString(Constants.APP_LANGUAGE)); final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final JSONArray jsonArray = new JSONArray();
String str = settingsHelper.getString(Constants.FOLDER_PATH); for (final DataBox.FavoriteModel favorite : allFavorites) {
if (!TextUtils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str); final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery());
str = settingsHelper.getString(Constants.DATE_TIME_FORMAT); jsonObject.put("type", favorite.getType().toString());
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_FORMAT, str); jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("pic_url", favorite.getPicUrl());
str = settingsHelper.getString(Constants.DATE_TIME_SELECTION); jsonObject.put("d", favorite.getDateAdded().getTime());
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_SELECTION, str); jsonArray.put(jsonObject);
}
str = settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT); return jsonArray;
if (!TextUtils.isEmpty(str)) json.put(Constants.CUSTOM_DATE_TIME_FORMAT, str); } catch (final Exception e) {
if (logCollector != null) {
json.put(Constants.DOWNLOAD_USER_FOLDER, settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER)); logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
json.put(Constants.MUTED_VIDEOS, settingsHelper.getBoolean(Constants.MUTED_VIDEOS)); }
json.put(Constants.AUTOPLAY_VIDEOS, settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); if (BuildConfig.DEBUG) {
json.put(Constants.AUTOLOAD_POSTS, settingsHelper.getBoolean(Constants.AUTOLOAD_POSTS)); Log.e(TAG, "Error exporting favorites", e);
json.put(Constants.FOLDER_SAVE_TO, settingsHelper.getBoolean(Constants.FOLDER_SAVE_TO));
result = json.toString();
} catch (final Exception e) {
result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getSettings");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} }
} }
return new JSONArray();
return result;
} }
@Nullable @NonNull
private static String getFavorites() { private static JSONArray getCookies() {
String result = null; if (Utils.dataBox == null) return new JSONArray();
if (Utils.dataBox != null) { try {
try { final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final ArrayList<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites(); final JSONArray jsonArray = new JSONArray();
final int allFavoritesSize; for (final DataBox.CookieModel cookie : allCookies) {
if (allFavorites != null && (allFavoritesSize = allFavorites.size()) > 0) { final JSONObject jsonObject = new JSONObject();
final JSONArray jsonArray = new JSONArray(); jsonObject.put("i", cookie.getUid());
for (int i = 0; i < allFavoritesSize; i++) { jsonObject.put("u", cookie.getUsername());
final DataBox.FavoriteModel favorite = allFavorites.get(i); jsonObject.put("c", cookie.getCookie());
final JSONObject jsonObject = new JSONObject(); jsonObject.put("full_name", cookie.getFullName());
jsonObject.put("q", favorite.getQuery()); jsonObject.put("profile_pic", cookie.getProfilePic());
jsonObject.put("d", favorite.getDate()); jsonArray.put(jsonObject);
jsonObject.put("s", favorite.getDisplayName()); }
jsonArray.put(jsonObject); return jsonArray;
} } catch (final Exception e) {
result = jsonArray.toString(); if (BuildConfig.DEBUG) {
} Log.e(TAG, "Error exporting accounts", e);
} catch (final Exception e) {
result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} }
} }
return result; return new JSONArray();
}
@Nullable
private static String getCookies() {
String result = null;
if (Utils.dataBox != null) {
try {
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final int allCookiesSize;
if (allCookies != null && (allCookiesSize = allCookies.size()) > 0) {
final JSONArray jsonArray = new JSONArray();
for (int i = 0; i < allCookiesSize; i++) {
final DataBox.CookieModel cookieModel = allCookies.get(i);
final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookieModel.getUid());
jsonObject.put("u", cookieModel.getUsername());
jsonObject.put("c", cookieModel.getCookie());
jsonArray.put(jsonObject);
}
result = jsonArray.toString();
}
} catch (final Exception e) {
result = null;
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
return result;
}
private final static class PasswordUtils {
private static final String cipherAlgo = "AES";
private static final String cipherTran = "AES/CBC/PKCS5Padding";
private static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP));
}
private static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
final byte[] bytes = cipher.doFinal(str.getBytes());
return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP);
}
} }
} }

View File

@ -34,7 +34,7 @@ public class NavigationExtensions {
int firstFragmentGraphId = 0; int firstFragmentGraphId = 0;
for (int i = 0; i < navGraphIds.size(); i++) { for (int i = 0; i < navGraphIds.size(); i++) {
final int navGraphId = navGraphIds.get(i); final int navGraphId = navGraphIds.get(i);
final String fragmentTag = getFragmentTag(i); final String fragmentTag = getFragmentTag(navGraphId);
final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId);
final NavController navController = navHostFragment.getNavController(); final NavController navController = navHostFragment.getNavController();
final int graphId = navController.getGraph().getId(); final int graphId = navController.getGraph().getId();
@ -57,7 +57,8 @@ public class NavigationExtensions {
return false; return false;
} }
String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
if (!selectedItemTag[0].equals(newlySelectedItemTag)) { String tag = selectedItemTag[0];
if (tag != null && !tag.equals(newlySelectedItemTag)) {
fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE); fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragment == null) { if (fragment == null) {
@ -176,7 +177,7 @@ public class NavigationExtensions {
final Intent intent) { final Intent intent) {
for (int i = 0; i < navGraphIds.size(); i++) { for (int i = 0; i < navGraphIds.size(); i++) {
final int navGraphId = navGraphIds.get(i); final int navGraphId = navGraphIds.get(i);
final String fragmentTag = getFragmentTag(i); final String fragmentTag = getFragmentTag(navGraphId);
final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId);
if (navHostFragment.getNavController().handleDeepLink(intent)) { if (navHostFragment.getNavController().handleDeepLink(intent)) {
final int selectedItemId = bottomNavigationView.getSelectedItemId(); final int selectedItemId = bottomNavigationView.getSelectedItemId();

View File

@ -0,0 +1,47 @@
package awais.instagrabber.utils;
import android.util.Base64;
import androidx.annotation.NonNull;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public final class PasswordUtils {
private static final String cipherAlgo = "AES";
private static final String cipherTran = "AES/CBC/PKCS5Padding";
public static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception {
try {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new IncorrectPasswordException(e);
}
}
public static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
final byte[] bytes = cipher.doFinal(str.getBytes());
return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP);
}
public static class IncorrectPasswordException extends Exception {
public IncorrectPasswordException(final GeneralSecurityException e) {
super(e);
}
}
}

View File

@ -232,8 +232,8 @@ public final class ResponseBodyUtils {
final String threadV2Id = data.getString("thread_v2_id"); final String threadV2Id = data.getString("thread_v2_id");
final String threadTitle = data.getString("thread_title"); final String threadTitle = data.getString("thread_title");
final String threadNewestCursor = data.getString("newest_cursor"); final String threadNewestCursor = data.optString("newest_cursor");
final String threadOldestCursor = data.getString("oldest_cursor"); final String threadOldestCursor = data.optString("oldest_cursor");
final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null; final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null;
final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null; final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null;

View File

@ -40,7 +40,7 @@ public final class SettingsHelper {
private final SharedPreferences sharedPreferences; private final SharedPreferences sharedPreferences;
public SettingsHelper(@NonNull final Context context) { public SettingsHelper(@NonNull final Context context) {
this.sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE); this.sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
} }
@NonNull @NonNull

View File

@ -1,28 +1,20 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.text.Editable;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.util.Pair;
import android.view.View;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
@ -39,11 +31,9 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogImportExportBinding; import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
public final class Utils { public final class Utils {
private static final String TAG = "Utils"; private static final String TAG = "Utils";
private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;
@ -64,19 +54,6 @@ public final class Utils {
return Math.round((dp * displayMetrics.densityDpi) / 160.0f); return Math.round((dp * displayMetrics.densityDpi) / 160.0f);
} }
public static void setTooltipText(final View view, @StringRes final int tooltipTextRes) {
if (view != null && tooltipTextRes != 0 && tooltipTextRes != -1) {
final Context context = view.getContext();
final String tooltipText = context.getResources().getString(tooltipTextRes);
if (Build.VERSION.SDK_INT >= 26) view.setTooltipText(tooltipText);
else view.setOnLongClickListener(v -> {
Toast.makeText(context, tooltipText, Toast.LENGTH_SHORT).show();
return true;
});
}
}
public static void copyText(@NonNull final Context context, final CharSequence string) { public static void copyText(@NonNull final Context context, final CharSequence string) {
if (clipboardManager == null) if (clipboardManager == null)
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
@ -89,100 +66,6 @@ public final class Utils {
Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show(); Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show();
} }
public static void showImportExportDialog(final Context context) {
final DialogImportExportBinding importExportBinding = DialogImportExportBinding.inflate(LayoutInflater.from(context));
final View passwordParent = (View) importExportBinding.cbPassword.getParent();
final View exportLoginsParent = (View) importExportBinding.cbExportLogins.getParent();
final View exportFavoritesParent = (View) importExportBinding.cbExportFavorites.getParent();
final View exportSettingsParent = (View) importExportBinding.cbExportSettings.getParent();
final View importLoginsParent = (View) importExportBinding.cbImportLogins.getParent();
final View importFavoritesParent = (View) importExportBinding.cbImportFavorites.getParent();
final View importSettingsParent = (View) importExportBinding.cbImportSettings.getParent();
importExportBinding.cbPassword.setOnCheckedChangeListener((buttonView, isChecked) ->
importExportBinding.etPassword.etPassword.setEnabled(isChecked));
final AlertDialog[] dialog = new AlertDialog[1];
final View.OnClickListener onClickListener = v -> {
if (v == passwordParent) importExportBinding.cbPassword.performClick();
else if (v == exportLoginsParent) importExportBinding.cbExportLogins.performClick();
else if (v == exportFavoritesParent)
importExportBinding.cbExportFavorites.performClick();
else if (v == importLoginsParent) importExportBinding.cbImportLogins.performClick();
else if (v == importFavoritesParent)
importExportBinding.cbImportFavorites.performClick();
else if (v == exportSettingsParent) importExportBinding.cbExportSettings.performClick();
else if (v == importSettingsParent) importExportBinding.cbImportSettings.performClick();
else if (context instanceof AppCompatActivity) {
final FragmentManager fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager();
final String folderPath = settingsHelper.getString(FOLDER_PATH);
if (v == importExportBinding.btnSaveTo) {
final Editable text = importExportBinding.etPassword.etPassword.getText();
final boolean passwordChecked = importExportBinding.cbPassword.isChecked();
if (passwordChecked && TextUtils.isEmpty(text))
Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show();
else {
new DirectoryChooser().setInitialDirectory(folderPath).setInteractionListener(path -> {
final File file = new File(path, "InstaGrabber_Settings_" + System.currentTimeMillis() + ".zaai");
final String password = passwordChecked ? text.toString() : null;
int flags = 0;
if (importExportBinding.cbExportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbExportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbExportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Export(password, flags, file, result -> {
Toast.makeText(context, result ? R.string.dialog_export_success : R.string.dialog_export_failed, Toast.LENGTH_SHORT)
.show();
if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss();
});
}).show(fragmentManager, null);
}
} else if (v == importExportBinding.btnImport) {
new DirectoryChooser().setInitialDirectory(folderPath).setShowZaAiConfigFiles(true).setInteractionListener(path -> {
int flags = 0;
if (importExportBinding.cbImportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbImportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbImportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Import(context, flags, new File(path), result -> {
((AppCompatActivity) context).recreate();
Toast.makeText(context, result ? R.string.dialog_import_success : R.string.dialog_import_failed, Toast.LENGTH_SHORT)
.show();
if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss();
});
}).show(fragmentManager, null);
}
}
};
passwordParent.setOnClickListener(onClickListener);
exportLoginsParent.setOnClickListener(onClickListener);
exportSettingsParent.setOnClickListener(onClickListener);
exportFavoritesParent.setOnClickListener(onClickListener);
importLoginsParent.setOnClickListener(onClickListener);
importSettingsParent.setOnClickListener(onClickListener);
importFavoritesParent.setOnClickListener(onClickListener);
importExportBinding.btnSaveTo.setOnClickListener(onClickListener);
importExportBinding.btnImport.setOnClickListener(onClickListener);
dialog[0] = new AlertDialog.Builder(context).setView(importExportBinding.getRoot()).show();
}
public static Map<String, String> sign(final Map<String, Object> form) { public static Map<String, String> sign(final Map<String, Object> form) {
final String signed = sign(new JSONObject(form).toString()); final String signed = sign(new JSONObject(form).toString());
if (signed == null) { if (signed == null) {
@ -251,4 +134,16 @@ public final class Utils {
} }
return simpleCache; return simpleCache;
} }
@Nullable
public static Pair<FavoriteType, String> migrateOldFavQuery(final String queryText) {
if (queryText.startsWith("@")) {
return new Pair<>(FavoriteType.USER, queryText.substring(1));
} else if (queryText.contains("/")) {
return new Pair<>(FavoriteType.LOCATION, queryText.substring(0, queryText.indexOf("/")));
} else if (queryText.startsWith("#")) {
return new Pair<>(FavoriteType.HASHTAG, queryText.substring(1));
}
return null;
}
} }

View File

@ -0,0 +1,19 @@
package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.utils.DataBox;
public class FavoritesViewModel extends ViewModel {
private MutableLiveData<List<DataBox.FavoriteModel>> list;
public MutableLiveData<List<DataBox.FavoriteModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

View File

@ -0,0 +1,18 @@
package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.io.File;
import java.util.List;
public class FileListViewModel extends ViewModel {
private MutableLiveData<List<File>> list;
public MutableLiveData<List<File>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

View File

@ -98,14 +98,12 @@ public class StoriesService extends BaseService {
public void getUserStory(final String id, public void getUserStory(final String id,
final String username, final String username,
final boolean storiesig,
final boolean isLoc, final boolean isLoc,
final boolean isHashtag, final boolean isHashtag,
final boolean highlight, final boolean highlight,
final ServiceCallback<List<StoryModel>> callback) { final ServiceCallback<List<StoryModel>> callback) {
final String url = buildUrl(id, storiesig, isLoc, isHashtag, highlight); final String url = buildUrl(id, isLoc, isHashtag, highlight);
final String userAgent = storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT; final Call<String> userStoryCall = repository.getUserStory(Constants.I_USER_AGENT, url);
final Call<String> userStoryCall = repository.getUserStory(userAgent, url);
userStoryCall.enqueue(new Callback<String>() { userStoryCall.enqueue(new Callback<String>() {
@Override @Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -119,7 +117,7 @@ public class StoriesService extends BaseService {
} }
data = new JSONObject(body); data = new JSONObject(body);
if (!storiesig && !highlight) if (!highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel"); data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
else if (highlight) data = data.getJSONObject("reels").optJSONObject(id); else if (highlight) data = data.getJSONObject("reels").optJSONObject(id);
@ -243,16 +241,10 @@ public class StoriesService extends BaseService {
}); });
} }
private String buildUrl(final String id, final boolean storiesig, final boolean isLoc, final boolean isHashtag, final boolean highlight) { private String buildUrl(final String id, final boolean isLoc, final boolean isHashtag, final boolean highlight) {
final String userId = id.replace(":", "%3A"); final String userId = id.replace(":", "%3A");
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append("https://"); builder.append("https://i.instagram.com/api/v1/");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
if (isLoc) { if (isLoc) {
builder.append("locations/"); builder.append("locations/");
} }
@ -266,11 +258,7 @@ public class StoriesService extends BaseService {
} }
builder.append(userId); builder.append(userId);
if (!highlight) { if (!highlight) {
if (storiesig) { builder.append("/story/");
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
} }
return builder.toString(); return builder.toString();
} }

View File

@ -0,0 +1,101 @@
package awais.instagrabber.webservices;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import awais.instagrabber.repositories.TagsRepository;
import awais.instagrabber.utils.Constants;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class TagsService extends BaseService {
private static final String TAG = "TagsService";
// web for www.instagram.com
private final TagsRepository webRepository;
private static TagsService instance;
private TagsService() {
final Retrofit webRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com/")
.build();
webRepository = webRetrofit.create(TagsRepository.class);
}
public static TagsService getInstance() {
if (instance == null) {
instance = new TagsService();
}
return instance;
}
public void follow(@NonNull final String tag,
@NonNull final String csrfToken,
final ServiceCallback<Boolean> callback) {
final Call<String> request = webRepository.follow(Constants.USER_AGENT,
csrfToken,
tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.onFailure(new RuntimeException("body is null"));
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
Log.e(TAG, "onResponse: ", e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
callback.onFailure(t);
}
});
}
public void unfollow(@NonNull final String tag,
@NonNull final String csrfToken,
final ServiceCallback<Boolean> callback) {
final Call<String> request = webRepository.unfollow(Constants.USER_AGENT,
csrfToken,
tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.onFailure(new RuntimeException("body is null"));
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
Log.e(TAG, "onResponse: ", e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
callback.onFailure(t);
}
});
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M24 17V19H21V17C21 15.45 20.3 14.06 19.18 13.06C24 13.55 24 17 24 17M18 5C19.66 5 21 6.34 21 8C21 9.66 19.66 11 18 11C17.69 11 17.38 10.95 17.1 10.86C17.67 10.05 18 9.07 18 8C18 6.94 17.67 5.95 17.1 5.14C17.38 5.05 17.69 5 18 5M13 5C14.66 5 16 6.34 16 8C16 9.66 14.66 11 13 11C11.34 11 10 9.66 10 8C10 6.34 11.34 5 13 5M19 17V19H7V17C7 14.79 9.69 13 13 13C16.31 13 19 14.79 19 17M.464 13.12L2.59 11L.464 8.88L1.88 7.46L4 9.59L6.12 7.46L7.54 8.88L5.41 11L7.54 13.12L6.12 14.54L4 12.41L1.88 14.54Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M11 7V13L16.2 16.1L17 14.9L12.5 12.2V7H11M20 12V18H22V12H20M20 20V22H22V20H20M18 20C16.3 21.3 14.3 22 12 22C6.5 22 2 17.5 2 12S6.5 2 12 2C16.8 2 20.9 5.4 21.8 10H19.7C18.8 6.6 15.7 4 12 4C7.6 4 4 7.6 4 12S7.6 20 12 20C14.4 20 16.5 18.9 18 17.3V20Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>

View File

@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal"> android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
</vector> </vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM9,4h2v5l-1,-0.75L9,9L9,4zM18,20L6,20L6,4h1v9l3,-2.25L13,13L13,4h5v16z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM15,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4zM9,18c0.22,-0.72 3.31,-2 6,-2 2.7,0 5.8,1.29 6,2L9,18zM6,15v-3h3v-2L6,10L6,7L4,7v3L1,10v2h3v3z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15,6c1.1,0 2,0.9 2,2 0,0.99 -0.73,1.82 -1.67,1.97l-2.31,-2.31C13.19,6.72 14.01,6 15,6m0,-2c-2.21,0 -4,1.79 -4,4 0,0.18 0.03,0.35 0.05,0.52l3.43,3.43c0.17,0.02 0.34,0.05 0.52,0.05 2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4zM16.69,14.16L22.53,20L23,20v-2c0,-2.14 -3.56,-3.5 -6.31,-3.84zM13.01,16.13L14.88,18L9,18c0.08,-0.24 0.88,-1.01 2.91,-1.57l1.1,-0.3M1.41,1.71L0,3.12l4,4L4,10L1,10v2h3v3h2v-3h2.88l2.51,2.51C9.19,15.11 7,16.3 7,18v2h9.88l4,4 1.41,-1.41L1.41,1.71zM6,10v-0.88l0.88,0.88L6,10z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM19,18h-4.83l-0.59,0.59L12,20.17l-1.59,-1.59 -0.58,-0.58L5,18L5,4h14v14zM12,11c1.65,0 3,-1.35 3,-3s-1.35,-3 -3,-3 -3,1.35 -3,3 1.35,3 3,3zM12,7c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM18,15.58c0,-2.5 -3.97,-3.58 -6,-3.58s-6,1.08 -6,3.58L6,17h12v-1.42zM8.48,15c0.74,-0.51 2.23,-1 3.52,-1s2.78,0.49 3.52,1L8.48,15z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C17.3 12 16.6 12.1 15.9 12.4L18.1 10.5L13.7 10.1L12 6.1L10.3 10.1L5.9 10.5L9.2 13.4L8.2 17.7L12 15.4L12.5 15.7C12.3 16.2 12.1 16.8 12.1 17.3L5.8 21M17 14V17H14V19H17V22H19V19H22V17H19V14H17Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
</vector>

View File

@ -0,0 +1,8 @@
<!-- drawable/star_check.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C14.9 12 12.4 14.3 12 17.3L5.8 21M17.8 21.2L22.6 16.4L21.3 15L17.7 18.6L16.2 17L15 18.2L17.8 21.2" />
</vector>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" > <shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/semi_transparent_black" /> <solid android:color="@color/black_a50" />
<padding <padding
android:left="2dp" android:left="2dp"
android:right="2dp" android:right="2dp"

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_outline_star_plus_24" android:state_checked="false" />
<item android:drawable="@drawable/ic_star_check_24" android:state_checked="true" />
<item android:drawable="@drawable/ic_outline_star_plus_24" />
</selector>

View File

@ -28,7 +28,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="none" app:layout_collapseMode="none"
app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface"
app:title="@string/app_name" app:title="@string/app_name"
tools:menu="@menu/main_menu" /> tools:menu="@menu/main_menu" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="0dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/dialog_export_settings" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportLogins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_export_accounts" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportFavorites"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_export_favorites" />
<include layout="@layout/item_pref_divider" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_password" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_no_max"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="32"
app:endIconMode="password_toggle"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="no"
android:inputType="textPassword"
android:maxLength="2200"
android:scrollHorizontally="false"
tools:text="test" />
</com.google.android.material.textfield.TextInputLayout>
<include layout="@layout/item_pref_divider" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveTo"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
android:text="@string/create_backup" />
</LinearLayout>

View File

@ -1,230 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingTop="10dp"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:paddingBottom="6dp"
android:singleLine="true"
android:text="@string/import_export" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportSettings"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportLogins"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_logins"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportFavorites"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_favorites"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbPassword"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/password"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
<include
android:id="@+id/etPassword"
layout="@layout/layout_password" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaveTo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/dialog_export_btn_export" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dp"
android:background="?android:attr/dividerVertical" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportSettings"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportLogins"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_logins"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportFavorites"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_favorites"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnImport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="8dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/dialog_export_btn_import" />
</LinearLayout>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/semi_transparent_black"> android:background="@color/black_a50">
<awais.instagrabber.customviews.drawee.ZoomableDraweeView <awais.instagrabber.customviews.drawee.ZoomableDraweeView
android:id="@+id/imageViewer" android:id="@+id/imageViewer"

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="0dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_chosen_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/file_chosen_label"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintBottom_toTopOf="@id/file_path"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_chosen_label"
tools:text="file path file path file path file path file path file path file path file path file path file path file path " />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbSettings"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/dialog_export_settings"
app:layout_constraintBottom_toTopOf="@id/cbAccounts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_path" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbAccounts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/dialog_export_accounts"
app:layout_constraintBottom_toTopOf="@id/cbFavorites"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbSettings" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbFavorites"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/dialog_export_favorites"
app:layout_constraintBottom_toTopOf="@id/top_password_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbAccounts" />
<include
android:id="@+id/top_password_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/enter_password_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbFavorites" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/enter_password_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:text="@string/enter_password"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/passwordField"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_password_divider" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="@string/password_no_max"
app:counterEnabled="true"
app:counterMaxLength="32"
app:endIconMode="password_toggle"
app:layout_constraintBottom_toTopOf="@id/bottom_password_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/enter_password_label">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="no"
android:inputType="textPassword"
android:maxLength="2200"
android:scrollHorizontally="false"
tools:text="test" />
</com.google.android.material.textfield.TextInputLayout>
<include
android:id="@+id/bottom_password_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/btn_restore"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/passwordField" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_restore"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
android:text="@string/restore_backup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" />
<androidx.constraintlayout.widget.Group
android:id="@+id/password_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="top_password_divider,bottom_password_divider,enter_password_label,passwordField"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/favorite_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_suggestion" />

View File

@ -17,47 +17,120 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll"> app:layout_scrollFlags="scroll">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tagInfoContainer" android:id="@+id/tagInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:padding="@dimen/profile_info_container_bottom_space">
android:padding="@dimen/profile_info_container_bottom_space"
android:visibility="visible">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage" android:id="@+id/mainHashtagImage"
android:layout_width="@dimen/profile_picture_size" android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size" android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true" android:background="?selectableItemBackgroundBorderless"
android:background="?selectableItemBackgroundBorderless" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/mainTagPostCount"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@mipmap/ic_launcher" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainTagPostCount" android:id="@+id/mainTagPostCount"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp" app:layout_constraintBottom_toTopOf="@id/btnFollowTag"
tools:text="35\nPosts" /> app:layout_constraintStart_toEndOf="@id/mainHashtagImage"
app:layout_constraintTop_toTopOf="@id/mainHashtagImage"
tools:text="35 Posts" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.chip.Chip
android:id="@+id/btnFollowTag" android:id="@+id/btnFollowTag"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_marginStart="8dp"
android:layout_weight="2" android:layout_marginLeft="8dp"
android:text="@string/follow" android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" /> app:chipBackgroundColor="@null"
</LinearLayout> app:chipIcon="@drawable/ic_outline_person_add_24"
app:chipIconTint="@color/deep_purple_800"
app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"
app:layout_constraintStart_toEndOf="@id/mainHashtagImage"
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
app:rippleColor="@color/purple_200" />
<com.google.android.material.chip.Chip
android:id="@+id/fav_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/add_to_favorites"
android:visibility="gone"
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_star_plus_24"
app:chipIconTint="@color/yellow_800"
app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"
app:layout_constraintStart_toEndOf="@id/btnFollowTag"
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
app:rippleColor="@color/yellow_400" />
<!--<com.google.android.material.chip.Chip-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount" />-->
<!--<com.google.android.material.button.MaterialButton-->
<!-- android:id="@+id/btnFollowTag"-->
<!-- style="@style/Widget.MaterialComponents.Button.TextButton"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:text="@string/follow"-->
<!-- android:textColor="@color/deep_purple_200"-->
<!-- android:visibility="gone"-->
<!-- app:icon="@drawable/ic_outline_person_add_24"-->
<!-- app:iconGravity="top"-->
<!-- app:iconTint="@color/deep_purple_200"-->
<!-- app:layout_constraintBottom_toTopOf="@id/fav_cb"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainTagPostCount"-->
<!-- app:layout_constraintTop_toTopOf="@id/mainHashtagImage"-->
<!-- tools:visibility="visible" />-->
<!--<CheckBox-->
<!-- android:id="@+id/fav_cb"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:button="@drawable/sl_favourite_24"-->
<!-- android:paddingStart="8dp"-->
<!-- android:paddingEnd="8dp"-->
<!-- android:text="Add to favorites"-->
<!-- android:visibility="gone"-->
<!-- app:buttonTint="@color/yellow_800"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintTop_toBottomOf="@id/btnFollowTag"-->
<!-- tools:visibility="gone" />-->
<!--<ProgressBar-->
<!-- android:id="@+id/fav_progress"-->
<!-- style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"-->
<!-- android:layout_width="24dp"-->
<!-- android:layout_height="24dp"-->
<!-- android:paddingStart="8dp"-->
<!-- android:paddingEnd="8dp"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
<!-- app:layout_constraintStart_toStartOf="@id/fav_cb"-->
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"-->
<!-- tools:visibility="gone" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -17,101 +17,128 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"> app:layout_scrollFlags="scroll|exitUntilCollapsed">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/locInfoContainer" android:id="@+id/locInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@null" android:background="@null"
android:orientation="vertical" android:padding="8dp">
android:paddingBottom="5dp">
<LinearLayout <awais.instagrabber.customviews.CircularImageView
android:id="@+id/locInfo" android:id="@+id/mainLocationImage"
android:layout_width="match_parent" android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:background="?selectableItemBackgroundBorderless"
app:actualImageScaleType="centerCrop"
app:layout_constraintEnd_toStartOf="@id/mainLocPostCount"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@mipmap/ic_launcher" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainLocPostCount"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center_vertical"
android:maxLines="1"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:textAppearance="@style/TextAppearance.AppCompat"
app:layout_constraintBottom_toTopOf="@id/btnMap"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/mainLocationImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="35 Posts" />
<com.google.android.material.chip.Chip
android:id="@+id/btnMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginStart="8dp"
android:padding="@dimen/profile_info_container_bottom_space"> android:layout_marginLeft="8dp"
android:text="@string/map"
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_map_24"
app:chipIconTint="@color/green_500"
app:layout_constraintBottom_toTopOf="@id/locationFullName"
app:layout_constraintStart_toEndOf="@id/mainLocationImage"
app:layout_constraintTop_toBottomOf="@id/mainLocPostCount"
app:rippleColor="@color/grey_500"
tools:visibility="visible" />
<awais.instagrabber.customviews.CircularImageView <com.google.android.material.chip.Chip
android:id="@+id/mainLocationImage" android:id="@+id/fav_chip"
android:layout_width="@dimen/profile_picture_size" android:layout_width="wrap_content"
android:layout_height="@dimen/profile_picture_size" android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless" android:layout_marginStart="8dp"
app:actualImageScaleType="fitCenter" /> android:layout_marginLeft="8dp"
android:text="@string/add_to_favorites"
<androidx.appcompat.widget.AppCompatTextView app:chipBackgroundColor="@null"
android:id="@+id/mainLocPostCount" app:chipIcon="@drawable/ic_outline_star_plus_24"
android:layout_width="0dp" app:chipIconTint="@color/yellow_800"
android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="@id/mainLocationImage"
android:layout_marginStart="12dp" app:layout_constraintStart_toEndOf="@id/btnMap"
android:layout_marginLeft="12dp" app:layout_constraintTop_toBottomOf="@id/mainLocPostCount"
android:layout_marginEnd="12dp" app:rippleColor="@color/yellow_400" />
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnMap"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:text="@string/map"
android:textColor="@color/btn_green_text_color"
android:textSize="20sp"
android:visibility="gone"
app:backgroundTint="@color/btn_green_background" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/locationFullName" android:id="@+id/locationFullName"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locInfo"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingTop="4dp"
android:paddingRight="10dp" android:paddingEnd="8dp"
android:paddingRight="8dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/locationBiography"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainLocationImage"
tools:text="OUR HOUSE" /> tools:text="OUR HOUSE" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationBiography" android:id="@+id/locationBiography"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locationFullName" android:layout_below="@id/locationFullName"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingEnd="8dp"
android:paddingRight="10dp" android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone" android:visibility="gone"
tools:text="IN THE MIDDLE OF OUR STREET" /> app:layout_constraintBottom_toTopOf="@id/locationUrl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/locationFullName"
tools:text="IN THE MIDDLE OF OUR STREET"
tools:visibility="visible" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationUrl" android:id="@+id/locationUrl"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/locationBiography" android:layout_below="@id/locationBiography"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="10dp" android:paddingStart="8dp"
android:paddingLeft="10dp" android:paddingLeft="8dp"
android:paddingEnd="10dp" android:paddingEnd="8dp"
android:paddingRight="10dp" android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone" android:visibility="gone"
tools:text="https://austinhuang.me/" /> app:layout_constraintBottom_toBottomOf="parent"
</RelativeLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/locationBiography"
tools:text="https://austinhuang.me/"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -79,8 +79,10 @@
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:paddingBottom="8dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold" android:textStyle="bold"
@ -102,15 +104,34 @@
app:srcCompat="@drawable/verified" app:srcCompat="@drawable/verified"
tools:visibility="visible" /> tools:visibility="visible" />
<CheckBox
android:id="@+id/fav_cb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@drawable/sl_favourite_24"
android:visibility="gone"
app:buttonTint="@color/yellow_800"
app:layout_constraintBaseline_toBaselineOf="@id/mainFullName"
app:layout_constraintBottom_toTopOf="@id/mainBiography"
app:layout_constraintStart_toEndOf="@id/isVerified" />
<ProgressBar
android:id="@+id/fav_progress"
style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/isVerified"
app:layout_constraintTop_toTopOf="@id/mainFullName"
tools:visibility="gone" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainBiography" android:id="@+id/mainBiography"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/mainUrl" app:layout_constraintBottom_toTopOf="@id/mainUrl"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -124,10 +145,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/mainBiography" android:layout_below="@id/mainBiography"
android:ellipsize="marquee" android:ellipsize="marquee"
android:paddingStart="8dp" android:padding="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -135,97 +153,96 @@
app:layout_constraintTop_toBottomOf="@id/mainBiography" app:layout_constraintTop_toBottomOf="@id/mainBiography"
tools:text="https://austinhuang.me/" tools:text="https://austinhuang.me/"
tools:textColor="@android:color/holo_blue_dark" tools:textColor="@android:color/holo_blue_dark"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnFollow" android:id="@+id/btnFollow"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/follow" android:text="@string/follow"
android:textColor="@color/btn_pink_text_color" android:textColor="@color/deep_purple_200"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" app:icon="@drawable/ic_outline_person_add_24"
app:layout_constraintBottom_toTopOf="@id/button_barrier" app:iconGravity="top"
app:layout_constraintEnd_toStartOf="@id/btnRestrict" app:iconTint="@color/deep_purple_200"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnTagged"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainUrl" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/purple_200"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnRestrict" android:id="@+id/btnTagged"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/restrict" android:text="@string/tagged"
android:textColor="@color/btn_orange_text_color" android:textColor="@color/deep_orange_600"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" app:icon="@drawable/ic_outline_person_pin_24"
app:layout_constraintBottom_toTopOf="@id/button_barrier" app:iconGravity="top"
app:layout_constraintEnd_toStartOf="@id/btnBlock" app:iconTint="@color/deep_orange_600"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnSaved"
app:layout_constraintStart_toEndOf="@id/btnFollow" app:layout_constraintStart_toEndOf="@id/btnFollow"
app:layout_constraintTop_toBottomOf="@id/mainUrl" app:layout_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnBlock"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/block"
android:textColor="@color/btn_red_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_red_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnRestrict"
app:layout_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/button_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="btnFollow, btnRestrict, btnBlock" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnTagged"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/tagged"
android:textColor="@color/btn_blue_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnSaved"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/button_barrier"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaved" android:id="@+id/btnSaved"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/saved" android:text="@string/saved"
android:textColor="@color/btn_orange_text_color" android:textColor="@color/blue_700"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" app:icon="@drawable/ic_outline_class_24"
app:iconGravity="top"
app:iconTint="@color/blue_700"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier" app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/btnLiked" app:layout_constraintEnd_toStartOf="@id/btnLiked"
app:layout_constraintStart_toEndOf="@id/btnTagged" app:layout_constraintStart_toEndOf="@id/btnTagged"
app:layout_constraintTop_toTopOf="@id/button_barrier" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/blue_A400"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnLiked" android:id="@+id/btnLiked"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/liked" android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color" android:textColor="@color/red_600"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_lightpink_background" app:icon="@drawable/ic_like"
app:iconGravity="top"
app:iconTint="@color/red_600"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier" app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnDM"
app:layout_constraintStart_toEndOf="@id/btnSaved" app:layout_constraintStart_toEndOf="@id/btnSaved"
app:layout_constraintTop_toTopOf="@id/button_barrier" app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/red_300"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDM"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/dm_person"
android:textColor="@color/green"
android:visibility="gone"
app:icon="@drawable/ic_send_24"
app:iconGravity="top"
app:iconTint="@color/green"
app:layout_constraintBottom_toTopOf="@id/highlights_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnLiked"
app:layout_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/green"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier

View File

@ -1,15 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/text1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:selectableItemBackground" android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" android:minHeight="56dp">
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" <ImageView
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" android:id="@+id/icon"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:layout_width="wrap_content"
android:paddingRight="?android:attr/listPreferredItemPaddingRight" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall" android:layout_gravity="center"
tools:viewBindingIgnore="true" /> android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="?attr/textAppearanceSubtitle1"
tools:text="Line line" />
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="?colorAccent"
android:textStyle="bold"
tools:text="HEADERS" />

View File

@ -18,7 +18,7 @@
android:id="@+id/media_list" android:id="@+id/media_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:background="@color/semi_transparent_black" /> tools:background="@color/black_a50" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:layout_width="24dp" android:layout_width="24dp"

View File

@ -22,10 +22,10 @@
android:layout_weight="1" android:layout_weight="1"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@null" android:background="@null"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:gravity="center"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:weightSum="2"> android:weightSum="2">
@ -36,7 +36,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/feed_text_primary_color"
tools:text="username" /> tools:text="username" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
@ -46,7 +45,6 @@
android:layout_below="@+id/title" android:layout_below="@+id/title"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="@color/feed_text_primary_color"
android:textSize="15sp" android:textSize="15sp"
android:visibility="visible" android:visibility="visible"
tools:text="location" /> tools:text="location" />

View File

@ -20,37 +20,6 @@
app:roundAsCircle="true" app:roundAsCircle="true"
tools:placeholderImage="@mipmap/ic_launcher" /> tools:placeholderImage="@mipmap/ic_launcher" />
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/multi_pic1"-->
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"-->
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"-->
<!-- app:actualImageScaleType="centerCrop"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/multi_pic2"-->
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"-->
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"-->
<!-- app:layout_constraintBottom_toTopOf="@+id/multi_pic3"-->
<!-- app:layout_constraintEnd_toStartOf="@id/barrier"-->
<!-- app:layout_constraintStart_toEndOf="@id/multi_pic1"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/multi_pic3"-->
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"-->
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toStartOf="@id/barrier"-->
<!-- app:layout_constraintStart_toEndOf="@id/multi_pic1"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/multi_pic2"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier" android:id="@+id/barrier"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -105,29 +74,10 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat" android:textAppearance="@style/TextAppearance.AppCompat"
app:layout_constraintEnd_toStartOf="@id/tvDate" app:layout_constraintEnd_toStartOf="@id/preview_barrier"
app:layout_constraintStart_toStartOf="@id/tvUsername" app:layout_constraintStart_toStartOf="@id/tvUsername"
app:layout_constraintTop_toBottomOf="@id/tvComment" app:layout_constraintTop_toBottomOf="@id/tvComment"
tools:text="sub-comment" /> tools:text="sub-comment long long long long long long long long long long" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:gravity="end"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="16dp"
android:paddingRight="16dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textStyle="italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivPreviewPic"
app:layout_constraintStart_toEndOf="@id/tvSubComment"
app:layout_constraintTop_toBottomOf="@id/tvComment"
tools:text="date" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/preview_barrier" android:id="@+id/preview_barrier"
@ -147,46 +97,22 @@
tools:placeholderImage="@mipmap/ic_launcher" tools:placeholderImage="@mipmap/ic_launcher"
tools:visibility="visible" /> tools:visibility="visible" />
<!--<com.facebook.drawee.view.SimpleDraweeView--> <androidx.appcompat.widget.AppCompatTextView
<!-- android:id="@+id/preview_pic1"--> android:id="@+id/tvDate"
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"--> android:layout_width="0dp"
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"--> android:layout_height="wrap_content"
<!-- app:layout_constraintBottom_toBottomOf="parent"--> android:ellipsize="marquee"
<!-- app:layout_constraintEnd_toStartOf="@id/preview_pic2"--> android:gravity="end"
<!-- app:layout_constraintStart_toEndOf="@id/preview_barrier"--> android:paddingStart="8dp"
<!-- app:layout_constraintTop_toTopOf="parent"--> android:paddingLeft="8dp"
<!-- app:placeholderImage="@mipmap/ic_launcher" />--> android:paddingEnd="16dp"
android:paddingRight="16dp"
<!--<com.facebook.drawee.view.SimpleDraweeView--> android:singleLine="true"
<!-- android:id="@+id/preview_pic2"--> android:textAppearance="@style/TextAppearance.AppCompat.Caption"
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"--> android:textStyle="italic"
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"--> app:layout_constraintBottom_toBottomOf="parent"
<!-- app:layout_constraintEnd_toEndOf="parent"--> app:layout_constraintEnd_toStartOf="@id/preview_barrier"
<!-- app:layout_constraintStart_toEndOf="@id/preview_pic1"--> app:layout_constraintStart_toEndOf="@id/ivProfilePic"
<!-- app:layout_constraintTop_toTopOf="parent"--> app:layout_constraintTop_toBottomOf="@id/tvSubComment"
<!-- app:placeholderImage="@mipmap/ic_launcher" />--> tools:text="some long long long long long date" />
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/preview_pic3"-->
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"-->
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/preview_pic1"-->
<!-- app:layout_constraintTop_toBottomOf="@id/preview_pic2"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<!--<androidx.constraintlayout.widget.Group-->
<!-- android:id="@+id/multi_pic_group"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:visibility="visible"-->
<!-- app:constraint_referenced_ids="multi_pic1, multi_pic2, multi_pic3" />-->
<!--<androidx.constraintlayout.widget.Group-->
<!-- android:id="@+id/preview_pic_group"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:visibility="gone"-->
<!-- app:constraint_referenced_ids="preview_pic1, preview_pic2, preview_pic3" />-->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,7 +7,10 @@
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="8dp"> android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"
@ -26,8 +29,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp" android:paddingStart="16dp"
android:paddingLeft="8dp" android:paddingLeft="16dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
@ -42,10 +45,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingStart="8dp" android:paddingStart="16dp"
android:paddingLeft="8dp" android:paddingLeft="16dp"
android:paddingEnd="8dp" android:paddingEnd="0dp"
android:paddingRight="8dp" android:paddingRight="0dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -1,116 +1,65 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<RelativeLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/footer" android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_alignParentTop="true"
android:background="@android:color/darker_gray" />
<View
android:id="@+id/horizontalDivider"
android:layout_width="1dip"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@android:color/darker_gray" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@id/horizontalDivider"
android:layout_toLeftOf="@id/horizontalDivider"
android:background="?android:selectableItemBackground"
android:text="@string/cancel" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_toEndOf="@id/horizontalDivider"
android:layout_toRightOf="@id/horizontalDivider"
android:background="?android:selectableItemBackground"
android:text="@string/confirm" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/directoryInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true"> app:layout_constraintBottom_toTopOf="@id/directoryList"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/btnNavUp" android:id="@+id/toolbar"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/nav_up"
android:padding="8dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_arrow_upward_24" />
<TextView
android:id="@+id/txtvSelectedFolderLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:layout_toEndOf="@id/btnNavUp"
android:layout_toRightOf="@id/btnNavUp"
android:text="@string/selected_folder_label"
android:textStyle="bold" />
<TextView
android:id="@+id/txtvSelectedFolder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/txtvSelectedFolderLabel" app:navigationIcon="@drawable/ic_arrow_upward_24"
android:layout_marginStart="8dp" tools:title="/this/that/thy" />
android:layout_marginLeft="8dp" </com.google.android.material.appbar.AppBarLayout>
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:layout_toEndOf="@id/btnNavUp"
android:layout_toRightOf="@id/btnNavUp"
android:ellipsize="start"
android:scrollHorizontally="true"
android:singleLine="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/btnNavUp"
android:background="@android:color/darker_gray" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/directoryList" android:id="@+id/directoryList"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_above="@id/footer" app:layout_constraintBottom_toTopOf="@id/bottom_horizontal_divider"
android:layout_below="@id/directoryInfo" /> app:layout_constraintEnd_toEndOf="parent"
</RelativeLayout> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<include
android:id="@+id/bottom_horizontal_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/btnCancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCancel"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:icon="@drawable/ic_close_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/confirm"
app:icon="@drawable/ic_check_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,7 +10,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:padding="8dp" android:padding="8dp"
android:src="@drawable/expired" /> app:srcCompat="@drawable/ic_clock_alert_outline_24" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -72,7 +72,8 @@
android:layout_marginLeft="15dip" android:layout_marginLeft="15dip"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:visibility="gone"> android:visibility="gone"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaveTo" android:id="@+id/btnSaveTo"

View File

@ -1,12 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <!--<item-->
android:id="@+id/favourites" <!-- android:id="@+id/favourites"-->
android:enabled="true" <!-- android:enabled="true"-->
android:icon="@drawable/ic_star_24" <!-- android:icon="@drawable/ic_star_24"-->
android:title="@string/title_favorites" <!-- android:title="@string/title_favorites"-->
android:visible="false" <!-- android:visible="false"-->
app:showAsAction="ifRoom" /> <!-- app:showAsAction="ifRoom" />-->
<item
android:id="@+id/block"
android:icon="@drawable/ic_block_24"
android:title="@string/block"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/restrict"
android:icon="@drawable/ic_highlight_off_24"
android:title="@string/restrict"
android:visible="false"
app:showAsAction="never" />
</menu> </menu>

View File

@ -34,6 +34,24 @@
app:nullable="true" /> app:nullable="true" />
</action> </action>
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/morePreferencesFragment" android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment" android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
@ -47,6 +65,12 @@
<action <action
android:id="@+id/action_morePreferencesFragment_to_notificationsViewer" android:id="@+id/action_morePreferencesFragment_to_notificationsViewer"
app:destination="@id/notificationsViewer" /> app:destination="@id/notificationsViewer" />
<action
android:id="@+id/action_morePreferencesFragment_to_favoritesFragment"
app:destination="@id/favoritesFragment" />
<action
android:id="@+id/action_morePreferencesFragment_to_backupPreferencesFragment"
app:destination="@id/backupPreferencesFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/settingsPreferencesFragment" android:id="@+id/settingsPreferencesFragment"
@ -69,4 +93,12 @@
android:id="@+id/themePreferencesFragment" android:id="@+id/themePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment" android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment"
android:label="@string/theme_settings" /> android:label="@string/theme_settings" />
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites" />
<fragment
android:id="@+id/backupPreferencesFragment"
android:name="awais.instagrabber.fragments.settings.BackupPreferencesFragment"
android:label="@string/backup_and_restore" />
</navigation> </navigation>

View File

@ -36,7 +36,7 @@
<action <action
android:id="@+id/action_global_profileFragment" android:id="@+id/action_global_profileFragment"
app:destination="@id/profileFragment"> app:destination="@id/profile_nav_graph">
<argument <argument
android:name="username" android:name="username"
android:defaultValue="" android:defaultValue=""
@ -74,6 +74,9 @@
<action <action
android:id="@+id/action_profileFragment_to_storyViewerFragment" android:id="@+id/action_profileFragment_to_storyViewerFragment"
app:destination="@id/storyViewerFragment" /> app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_profileFragment_to_dMThreadFragment"
app:destination="@id/directMessagesThreadFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/savedViewerFragment" android:id="@+id/savedViewerFragment"
@ -145,4 +148,31 @@
app:argType="string" app:argType="string"
app:nullable="true" /> app:nullable="true" />
</fragment> </fragment>
<fragment
android:id="@+id/directMessagesThreadFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment"
android:label="DirectMessagesThreadFragment"
tools:layout="@layout/fragment_direct_messages_thread">
<argument
android:name="threadId"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
<action
android:id="@+id/action_dMThreadFragment_to_dMSettingsFragment"
app:destination="@id/directMessagesSettingsFragment" />
</fragment>
<fragment
android:id="@+id/directMessagesSettingsFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment"
android:label="DirectMessagesSettingsFragment"
tools:layout="@layout/fragment_direct_messages_settings">
<argument
android:name="threadId"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
</fragment>
</navigation> </navigation>

View File

@ -42,7 +42,6 @@
</string-array> </string-array>
<string-array name="anonymous_story_viewer"> <string-array name="anonymous_story_viewer">
<item>Deaktivieren</item> <item>Deaktivieren</item>
<item>storiesig</item>
<item>Aloinstagram</item> <item>Aloinstagram</item>
<item>Instadp</item> <item>Instadp</item>
</string-array> </string-array>

View File

@ -42,7 +42,6 @@
</string-array> </string-array>
<string-array name="anonymous_story_viewer"> <string-array name="anonymous_story_viewer">
<item>Disable</item> <item>Disable</item>
<item>storiesig</item>
<item>Aloinstagram</item> <item>Aloinstagram</item>
<item>Instadp</item> <item>Instadp</item>
</string-array> </string-array>

View File

@ -42,7 +42,6 @@
</string-array> </string-array>
<string-array name="anonymous_story_viewer"> <string-array name="anonymous_story_viewer">
<item>غیرفعال</item> <item>غیرفعال</item>
<item>استوری های ig</item>
<item>Aloinstagram</item> <item>Aloinstagram</item>
<item>Instadp</item> <item>Instadp</item>
</string-array> </string-array>

View File

@ -42,7 +42,6 @@
</string-array> </string-array>
<string-array name="anonymous_story_viewer"> <string-array name="anonymous_story_viewer">
<item>Disable</item> <item>Disable</item>
<item>storiesig</item>
<item>Aloinstagram</item> <item>Aloinstagram</item>
<item>Instadp</item> <item>Instadp</item>
</string-array> </string-array>

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