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,
"commit": false,
"badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg)](#contributors)",
"contributors": [
{
"login": "austinhuang0131",
@ -79,8 +80,7 @@
"blog",
"bug",
"ideas",
"question",
"userTesting"
"question"
]
},
{
@ -89,8 +89,18 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/53869451",
"profile": "https://airikr.me/",
"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"
]
},
{
"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",
"name": "kernoeb",
@ -167,10 +186,17 @@
}
],
"contributorsPerLine": 6,
"projectName": "instagrabber",
"projectName": "barinsta",
"projectOwner": "austinhuang0131",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
"types": {
"translation": {
"symbol": "🌍",
"description": "Translation",
"link": "https://crowdin.com/project/instagrabber"
}
},
"commitConvention": "none"
}
}

4
.gitignore vendored
View File

@ -9,11 +9,11 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/git_toolbox_prj.xml
/.idea/dbnavigator.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
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/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)
[![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).
@ -34,37 +36,35 @@ Version status: ![F-Droid](https://img.shields.io/f-droid/v/me.austinhuang.insta
### Contributors
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![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).
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-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<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://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/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="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="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://austinhuang.me"><img src="https://avatars1.githubusercontent.com/u/16656689?s=100" width="100px;" alt=""/><br /><sub><b>Austin Huang</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Documentation">📖</a> <a href="#question-austinhuang0131" title="Answering Questions">💬</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a> <a href="#ideas-austinhuang0131" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/ammargitham"><img src="https://avatars0.githubusercontent.com/u/8017365?s=100" width="100px;" alt=""/><br /><sub><b>Ammar Githam</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=ammargitham" title="Code">💻</a> <a href="#design-ammargitham" title="Design">🎨</a> <a href="#ideas-ammargitham" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-ammargitham" title="Maintenance">🚧</a> <a href="#question-ammargitham" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/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/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="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>
</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://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://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/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://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/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/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="#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/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/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://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/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>
<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://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/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/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://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/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/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/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>
</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)
[![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)
[![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)
[![Crowdin](https://badges.crowdin.net/instagrabber/localized.svg)](https://crowdin.com/project/instagrabber)

View File

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

View File

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

View File

@ -1,11 +1,10 @@
package awais.instagrabber;
import android.app.Application;
import android.content.ClipboardManager;
import android.content.Context;
import android.util.Log;
import androidx.multidex.MultiDexApplication;
import com.facebook.drawee.backends.pipeline.Fresco;
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.settingsHelper;
public final class InstaGrabberApplication extends MultiDexApplication {
public final class InstaGrabberApplication extends Application {
private static final String TAG = "InstaGrabberApplication";
@Override

View File

@ -1,6 +1,5 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@ -29,6 +28,7 @@ import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.navigation.NavBackStackEntry;
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.CollapsingToolbarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList;
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.Utils.settingsHelper;
public class MainActivity extends BaseLanguageActivity {
public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener {
private static final String TAG = "MainActivity";
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.directMessagesSettingsFragment,
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 List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
@ -106,6 +109,7 @@ public class MainActivity extends BaseLanguageActivity {
private Handler suggestionsFetchHandler;
private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
@ -152,6 +156,11 @@ public class MainActivity extends BaseLanguageActivity {
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
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
@ -211,6 +220,21 @@ public class MainActivity extends BaseLanguageActivity {
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() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
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;
if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
binding.bottomNavView.getMenu().clear();
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 = 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();
if (setDefaultFromSettings || !isLoggedIn) {
final List<Integer> mainNavList = getMainNavList(main_nav_ids);
if (setDefaultFromSettings) {
final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
final int defaultNavId = TextUtils.isEmpty(defaultTabIdString) || !isLoggedIn
final int defaultNavId = TextUtils.isEmpty(defaultTabIdString)
? R.navigation.profile_nav_graph
: Integer.parseInt(defaultTabIdString);
final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) {
firstFragmentGraphIndex = index;
final Integer menuId = NAV_TO_MENU_ID_MAP.get(defaultNavId);
if (menuId != null) {
binding.bottomNavView.setSelectedItemId(menuId);
}
}
if (index >= 0) firstFragmentGraphIndex = index;
setBottomNavSelectedItem(defaultNavId);
} catch (NumberFormatException e) {
Log.e(TAG, "Error parsing id", e);
}
@ -408,6 +426,27 @@ public class MainActivity extends BaseLanguageActivity {
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) {
NavigationUI.setupWithNavController(binding.toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
@ -582,4 +621,9 @@ public class MainActivity extends BaseLanguageActivity {
unbindService(serviceConnection);
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
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.databinding.ItemNotificationBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
public final class NotificationsAdapter extends ListAdapter<NotificationModel, NotificationViewHolder> {
private final OnNotificationClickListener notificationClickListener;
@ -49,6 +55,36 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
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 {
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());
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();
// binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
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();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
if (feedModels[feedModels.length - 1] != null)
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor);
final int length = feedModels.length;
if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
}
result = feedModels;
}

View File

@ -8,6 +8,10 @@ import androidx.annotation.Nullable;
import org.json.JSONArray;
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.URL;
@ -21,6 +25,8 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
private static final String TAG = "HashtagFetcher";
private final FetchListener<HashtagModel> fetchListener;
private final String hashtag;
@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
HashtagModel result = null;
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.connect();
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");
if (timelineMedia.has("edges")) {
@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
user.getString("profile_pic_url"),
timelineMedia.getLong("count"),
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();
} catch (final Exception e) {
if (logCollector != null)
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;

View File

@ -19,25 +19,23 @@ import awais.instagrabber.utils.NetworkUtils;
public final class HighlightsFetcher extends AsyncTask<Void, Void, List<HighlightModel>> {
private final String id;
private final boolean storiesig;
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.storiesig = storiesig;
this.fetchListener = fetchListener;
}
@Override
protected List<HighlightModel> doInBackground(final Void... voids) {
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 {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(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();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
@ -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.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 final PostItemType type;
private final String endCursor;
private final String id;
private final FetchListener<PostModel[]> fetchListener;
private final FetchListener<List<PostModel>> fetchListener;
private String username = null;
public PostsFetcher(final String id,
final PostItemType type,
final String endCursor,
final FetchListener<PostModel[]> fetchListener) {
final FetchListener<List<PostModel>> fetchListener) {
this.id = id;
this.type = type;
this.endCursor = endCursor == null ? "" : endCursor;
@ -52,7 +54,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
}
@Override
protected PostModel[] doInBackground(final Void... voids) {
protected List<PostModel> doInBackground(final Void... voids) {
// final boolean isHashTag = id.charAt(0) == '#';
// final boolean isSaved = id.charAt(0) == '$';
// final boolean isTagged = id.charAt(0) == '%';
@ -79,7 +81,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
default:
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 {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
@ -126,8 +128,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
}
final JSONArray edges = mediaPosts.getJSONArray("edges");
final PostModel[] models = new PostModel[edges.length()];
for (int i = 0; i < models.length; ++i) {
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
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 itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID),
mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"),
mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0).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"));
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]);
final PostModel model = new PostModel(
itemType,
mediaNode.getString(Constants.EXTRAS_ID),
mediaNode.getString("display_url"),
mediaNode.getString("thumbnail_src"),
mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0)
.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)
models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models;
if (!result.isEmpty() && result.get(result.size() - 1) != null)
result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor);
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
if (logCollector != null) {
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;
}
@Override
protected void onPostExecute(final PostModel[] postModels) {
protected void onPostExecute(final List<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
conn.connect();
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));
conn.disconnect();
@ -63,7 +62,7 @@ public final class SuggestionsFetcher extends AsyncTask<String, String, Suggesti
suggestionModels.add(new SuggestionModel(false,
hashtag.getString(Constants.EXTRAS_NAME),
null,
hashtag.optString("profile_pic_url", defaultHashTagPic),
hashtag.optString("profile_pic_url", Constants.DEFAULT_HASH_TAG_PIC),
SuggestionType.TYPE_HASHTAG,
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.util.Log;
@ -9,7 +9,6 @@ import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.NetworkUtils;
@ -17,16 +16,16 @@ import awais.instagrabber.utils.Utils;
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 final String cookie;
private final StoryModel storyModel;
private final String userId;
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.storyModel = storyModel;
this.userId = userId;
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]
+ "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie)
+ "\",\"__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");
if (urlParameters != null) {

View File

@ -10,6 +10,8 @@ import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
@ -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.Utils.logCollector;
public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
private final String endCursor;
private final FetchListener<PostModel[]> fetchListener;
public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>> {
private static final String TAG = "iLikedFetcher";
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.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.fetchListener = fetchListener;
}
@Override
protected PostModel[] doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id="+endCursor;
protected List<PostModel> doInBackground(final Void... voids) {
final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor;
PostModel[] result = null;
List<PostModel> result = new ArrayList<>();
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, PostModel[]> {
}
final JSONArray edges = body.getJSONArray("items");
final PostModel[] models = new PostModel[edges.length()];
for (int i = 0; i < models.length; ++i) {
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i);
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 itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID),
isSlider
? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider
? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
final PostModel model = new PostModel(
itemType,
mediaNode.getString(Constants.EXTRAS_ID),
isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getHighQualityImage(mediaNode),
isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media")
.getJSONObject(0))
: ResponseBodyUtils.getLowQualityImage(mediaNode),
mediaNode.getString("code"),
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"), true,
mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count"));
mediaNode.getLong("taken_at"),
true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username");
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;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
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);
}
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]);
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
if (models[models.length - 1] != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models;
final int length = result.size();
if (length >= 1 && result.get(length - 1) != null) {
result.get(length - 1).setPageCursor(hasNextPage, endCursor);
}
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
if (logCollector != null) {
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;
}
@Override
protected void onPostExecute(final PostModel[] postModels) {
protected void onPostExecute(final List<PostModel> 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 final boolean isLoc;
private final boolean isHashtag;
private final boolean storiesig;
private final boolean highlight;
private final FetchListener<StoryModel[]> fetchListener;
@ -38,14 +37,12 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
final String username,
final boolean isLoc,
final boolean isHashtag,
final boolean storiesig,
final boolean highlight,
final FetchListener<StoryModel[]> fetchListener) {
this.id = id;
this.username = username;
this.isLoc = isLoc;
this.isHashtag = isHashtag;
this.storiesig = storiesig;
this.highlight = highlight;
this.fetchListener = fetchListener;
}
@ -55,13 +52,7 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
StoryModel[] result = null;
final String userId = id.replace(":", "%3A");
final StringBuilder builder = new StringBuilder();
builder.append("https://");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
builder.append("https://i.instagram.com/api/v1/");
if (isLoc) {
builder.append("locations/");
}
@ -75,24 +66,20 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
}
builder.append(userId);
if (!highlight) {
if (storiesig) {
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
builder.append("/story/");
}
final String url = builder.toString();
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(true);
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.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn));
if (!storiesig && !highlight)
if (!highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
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;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
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.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.R;
@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
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 awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
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 MainActivity fragmentActivity;
@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment {
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private StoryModel[] storyModels;
private TagsService tagsService;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment {
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result));
List<PostModel> finalList = postModels == null || postModels.isEmpty()
? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
finalList.addAll(result);
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (result.length != 0) {
model = result[result.length - 1];
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
}
if (model == null) return;
endCursor = model.getEndCursor();
@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment {
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance();
}
@Nullable
@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment {
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchHashtagModel();
}
@Override
public void onDestroy() {
super.onDestroy();
@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment {
private void fetchPosts() {
stopCurrentExecutor();
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.swipeRefreshLayout.setRefreshing(true);
if (TextUtils.isEmpty(hashtag)) return;
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
@ -265,30 +286,110 @@ public class HashTagFragment extends Fragment {
final Context context = getContext();
if (context == null) return;
if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> {
storyModels = stories;
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, stories -> {
if (stories != null && stories.length > 0) {
binding.mainHashtagImage.setStoriesBorder();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf(
ContextCompat.getColor(context, hashtagModel.getFollowing()
? R.color.btn_purple_background
: R.color.btn_pink_background)));
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
? R.drawable.ic_outline_person_add_disabled_24
: R.drawable.ic_outline_person_add_24);
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 {
binding.btnFollowTag.setText(Utils.dataBox.getFavorite(hashtag) != null
? 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)));
binding.btnFollowTag.setVisibility(View.GONE);
}
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());
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 StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
binding.mainTagPostCount.setText(span);
@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment {
if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler();
handler.postDelayed(() -> {
actionBar.setTitle(hashtag);
}, 200);
handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
}
}

View File

@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
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.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.R;
@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
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;
@ -62,7 +67,7 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
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 MainActivity fragmentActivity;
@ -78,7 +83,7 @@ public class LocationFragment extends Fragment {
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn;
private StoryModel[] storyModels;
private boolean isPullToRefresh;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
@ -119,20 +124,25 @@ public class LocationFragment extends Fragment {
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null) return;
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
finalList.addAll(Arrays.asList(result));
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
PostModel model = null;
if (result.length != 0) {
model = result[result.length - 1];
if (!result.isEmpty()) {
model = result.get(result.size() - 1);
}
if (model == null) return;
endCursor = model.getEndCursor();
@ -164,10 +174,18 @@ public class LocationFragment extends Fragment {
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchLocationModel();
}
@Override
public void onDestroy() {
super.onDestroy();
@ -182,6 +200,8 @@ public class LocationFragment extends Fragment {
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
locationId = fragmentArgs.getLocationId();
binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE);
setTitle();
setupPosts();
fetchLocationModel();
@ -273,9 +293,7 @@ public class LocationFragment extends Fragment {
true,
false,
false,
false,
stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) {
binding.mainLocationImage.setStoriesBorder();
}
@ -283,7 +301,7 @@ public class LocationFragment extends Fragment {
}
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
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));
span.setSpan(new RelativeSizeSpan(1.2f), 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.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() {

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
@ -30,8 +29,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
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.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.ProfileNavGraphDirections;
@ -56,6 +56,7 @@ import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
@ -71,6 +72,7 @@ import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.models.enums.StoryViewerChoice;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -111,11 +113,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private StoryModel[] storyModels;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<Void, Void, PostModel[]> currentlyExecuting;
private MenuItem favMenuItem;
private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem;
private MenuItem restrictMenuItem;
private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -161,11 +164,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final PostModel[] result) {
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.length <= 0) {
if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
@ -175,15 +178,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
final List<PostModel> resultList = Arrays.asList(result);
if (isPullToRefresh) {
finalList = resultList;
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(resultList);
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result[result.length - 1];
final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage();
@ -262,7 +264,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
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
@ -295,6 +371,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
if (TextUtils.isEmpty(username) && !isLoggedIn) {
binding.infoContainer.setVisibility(View.GONE);
binding.swipeRefreshLayout.setEnabled(false);
binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24);
binding.privatePage2.setText(R.string.no_acc);
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);
return;
}
binding.swipeRefreshLayout.setEnabled(true);
setupPosts();
setupHighlights();
setupCommonListeners();
@ -337,17 +415,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void fetchProfileDetails() {
if (TextUtils.isEmpty(username)) return;
new ProfileFetcher(username.substring(1), profileModel -> {
if (getContext() == null) return;
this.profileModel = profileModel;
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
final boolean isSelf = isLoggedIn
&& profileModel != null
&& userIdFromCookie != null
&& userIdFromCookie.equals(profileModel.getId());
if (favMenuItem != null) {
favMenuItem.setVisible(isSelf);
}
// final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn
// && profileModel != null
// && userIdFromCookie != null
// && userIdFromCookie.equals(profileModel.getId());
// if (favMenuItem != null) {
// favMenuItem.setVisible(isSelf);
// }
setProfileDetails();
}).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);
final String profileId = profileModel.getId();
if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()) || isLoggedIn) {
if (isLoggedIn) {
new iStoryStatusFetcher(profileId,
profileModel.getUsername(),
false,
false,
!isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()),
false,
result -> {
storyModels = result;
@ -377,7 +455,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId,
!isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()),
result -> {
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
@ -385,7 +462,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} 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>>() {
@Override
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) {
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved);
ViewCompat.setBackgroundTintList(binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
} else {
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background)));
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
binding.btnRestrict.setVisibility(View.VISIBLE);
if (profileModel.getRestricted()) {
binding.btnRestrict.setText(R.string.unrestrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else {
binding.btnRestrict.setText(R.string.restrict);
ViewCompat.setBackgroundTintList(binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background)));
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnBlock.setVisibility(View.VISIBLE);
binding.btnTagged.setVisibility(View.VISIBLE);
if (profileModel.getBlocked()) {
binding.btnBlock.setText(R.string.unblock);
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background)));
} else {
binding.btnBlock.setText(R.string.block);
ViewCompat.setBackgroundTintList(binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background)));
binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
}
} else {
if (Utils.dataBox.getFavorite(username) != null) {
binding.btnFollow.setText(R.string.unfavorite_short);
ViewCompat.setBackgroundTintList(binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background)));
} else {
binding.btnFollow.setText(R.string.favorite_short);
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 (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
}
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());
final long followersCount = profileModel.getFollowersCount();
@ -563,23 +632,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId());
final String favorite = Utils.dataBox.getFavorite(username);
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()) {
friendshipService.unfollow(
userIdFromCookie,
@ -589,7 +642,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unfollow success: " + result);
fetchProfileDetails();
onRefresh();
}
@Override
@ -606,7 +659,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Follow success: " + result);
fetchProfileDetails();
onRefresh();
}
@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 -> {
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(),
profileModel.getId(),
@ -692,6 +687,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED);
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 -> {
if (storyModels == null || storyModels.length <= 0) {
// show profile pic
@ -721,6 +722,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setNegativeButton(R.string.cancel, null)
.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() {

View File

@ -29,12 +29,13 @@ public class AboutFragment extends BasePreferencesFragment {
final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context);
screen.addPreference(thirdPartyCategory);
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);
// alphabetical order!!!
thirdPartyCategory.addPreference(getExoPlayerPreference());
thirdPartyCategory.addPreference(getFrescoPreference());
thirdPartyCategory.addPreference(getJsoupPreference());
thirdPartyCategory.addPreference(getMDIPreference());
thirdPartyCategory.addPreference(getRetrofitPreference());
final PreferenceCategory licenseCategory = new PreferenceCategory(context);
@ -157,6 +158,22 @@ public class AboutFragment extends BasePreferencesFragment {
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() {
final Context context = getContext();
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
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName("settings");
preferenceManager.setSharedPreferencesName(Constants.SHARED_PREFERENCES_NAME);
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
final Context context = getContext();
if (context == null) return;

View File

@ -20,7 +20,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
@ -55,11 +55,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
accountCategory.setTitle(R.string.account);
accountCategory.setIconSpaceReserved(false);
screen.addPreference(accountCategory);
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (isLoggedIn) {
accountCategory.setSummary(R.string.account_hint);
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;
CookieUtils.setupCookies("LOGOUT");
shouldRecreate();
@ -79,7 +79,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
}
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;
new AlertDialog.Builder(getContext())
.setTitle(R.string.logout)
@ -96,37 +96,49 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
}));
}
final PreferenceCategory generalCategory = new PreferenceCategory(context);
generalCategory.setTitle(R.string.pref_category_general);
generalCategory.setIconSpaceReserved(false);
screen.addPreference(generalCategory);
// final PreferenceCategory generalCategory = new PreferenceCategory(context);
// generalCategory.setTitle(R.string.pref_category_general);
// generalCategory.setIconSpaceReserved(false);
// screen.addPreference(generalCategory);
screen.addPreference(getDivider(context));
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();
NavHostFragment.findNavController(this).navigate(navDirections);
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();
NavHostFragment.findNavController(this).navigate(navDirections);
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();
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
});
generalCategory.addPreference(aboutPreference);
}));
screen.addPreference(getDivider(context));
final Preference versionPreference = getPreference(R.string.version,
BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", -1, preference -> {
FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true);
return true;
});
screen.addPreference(versionPreference);
screen.addPreference(getPreference(R.string.version,
BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
-1,
preference -> {
FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true);
return true;
}));
screen.addPreference(getDivider(context));
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;
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(path -> {
settingsHelper.putString(FOLDER_PATH, path);
resultCallback.onResult(path);
.setInteractionListener(file -> {
settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
resultCallback.onResult(file.getAbsolutePath());
})
.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 {
NONE(0),
STORIESIG(1),
ALOINSTAGRAM(2),
INSTADP(3);
ALOINSTAGRAM(1),
INSTADP(2);
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.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
@ -115,18 +116,24 @@ public class ActivityCheckerService extends Service {
}
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)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.drawable.ic_notif)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentTitle(getString(R.string.action_notif))
.setContentText(notificationString)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(getPendingIntent())
.build();
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 PREF_DARK_THEME = "dark_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.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
@ -24,12 +27,9 @@ public final class DataBox extends SQLiteOpenHelper {
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_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_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_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) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
@ -47,7 +52,6 @@ public final class DataBox extends SQLiteOpenHelper {
private DataBox(@Nullable final Context context) {
super(context, "cookiebox.db", null, VERSION);
c = context;
}
@Override
@ -60,42 +64,120 @@ public final class DataBox extends SQLiteOpenHelper {
+ KEY_COOKIE + " TEXT,"
+ KEY_FULL_NAME + " 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!");
}
@Override
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));
if (oldVersion == 1) {
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
// switch without break, so that all migrations from a previous version to new are run
switch (oldVersion) {
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));
}
public final void addFavorite(@NonNull final FavoriteModel favoriteModel) {
final String query = favoriteModel.getQuery();
final String display = favoriteModel.getDisplayName();
@NonNull
private List<FavoriteModel> backupOldFavorites(@NonNull final SQLiteDatabase db) {
// 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)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final ContentValues values = new ContentValues();
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);
addOrUpdateFavorite(db, model);
db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error adding/updating favorite", e);
}
} finally {
db.endTransaction();
}
@ -103,23 +185,46 @@ public final class DataBox extends SQLiteOpenHelper {
}
}
public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) {
final String query = favoriteModel.getQuery();
private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
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)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?",
new String[]{query, Long.toString(favoriteModel.getDate())});
final int rowsDeleted = db.delete(TABLE_FAVORITES,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?",
new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())});
if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful();
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite");
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e);
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally {
db.endTransaction();
}
@ -127,76 +232,87 @@ public final class DataBox extends SQLiteOpenHelper {
}
}
@Nullable
public final ArrayList<FavoriteModel> getAllFavorites() {
ArrayList<FavoriteModel> favorites = null;
FavoriteModel tempFav;
@NonNull
public final List<FavoriteModel> getAllFavorites() {
final List<FavoriteModel> favorites = new ArrayList<>();
final SQLiteDatabase db = getWritableDatabase();
try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) {
try (final Cursor cursor = db.rawQuery("SELECT "
+ 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()) {
db.beginTransaction();
favorites = new ArrayList<>();
FavoriteModel tempFav;
do {
FavoriteType type = null;
try {
type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
tempFav = new FavoriteModel(
(cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor.getString(0).contains("/"))
? cursor.getString(0)
: "@" + cursor.getString(0), // query text
cursor.getLong(1), // date added
cursor.getString(2) == null ? (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor
.getString(0).contains("/"))
? cursor.getString(0)
: "@" + cursor.getString(0) : cursor.getString(2) // display
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
type,
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)))
);
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);
} while (cursor.moveToNext());
db.endTransaction();
}
} catch (final Exception x) {
Log.e("austin_debug", "", x);
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);
}
} catch (final Exception e) {
Log.e(TAG, "", e);
}
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();
final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE "
+ KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) {
final Cursor cursor = db.rawQuery("SELECT "
+ 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()) {
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;
}
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,
final String username,
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
public final CookieModel getCookie(final String uid) {
CookieModel cookie = null;
@ -297,10 +404,9 @@ public final class DataBox extends SQLiteOpenHelper {
return cookie;
}
@Nullable
public final ArrayList<CookieModel> getAllCookies() {
ArrayList<CookieModel> cookies = null;
@NonNull
public final List<CookieModel> getAllCookies() {
final List<CookieModel> cookies = new ArrayList<>();
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery(
"SELECT "
@ -312,7 +418,6 @@ public final class DataBox extends SQLiteOpenHelper {
+ " FROM " + TABLE_COOKIES, null)
) {
if (cursor != null && cursor.moveToFirst()) {
cookies = new ArrayList<>();
do {
cookies.add(new CookieModel(
cursor.getString(cursor.getColumnIndex(KEY_UID)),
@ -324,7 +429,6 @@ public final class DataBox extends SQLiteOpenHelper {
} while (cursor.moveToNext());
}
}
return cookies;
}
@ -376,6 +480,12 @@ public final class DataBox extends SQLiteOpenHelper {
this.selected = selected;
}
public boolean isValid() {
return !TextUtils.isEmpty(uid)
&& !TextUtils.isEmpty(username)
&& !TextUtils.isEmpty(cookie);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@ -394,36 +504,92 @@ public final class DataBox extends SQLiteOpenHelper {
@NonNull
@Override
public String toString() {
return username;
return "CookieModel{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", cookie='" + cookie + '\'' +
", fullName='" + fullName + '\'' +
", profilePic='" + profilePic + '\'' +
", selected=" + selected +
'}';
}
}
public static class FavoriteModel {
private final String query, displayName;
private final long date;
private final int id;
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.date = date;
this.type = type;
this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
}
public String getQuery() {
return query;
}
public FavoriteType getType() {
return type;
}
public String getDisplayName() {
return displayName;
}
public long getDate() {
return date;
public String getPicUrl() {
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
@Override
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.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
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.util.ArrayList;
@ -24,22 +30,27 @@ import java.util.Collections;
import java.util.List;
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 {
private static final String TAG = "DirectoryChooser";
public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY";
private static final File sdcardPathFile = Environment.getExternalStorageDirectory();
private static final String sdcardPath = sdcardPathFile.getPath();
private final List<String> fileNames = new ArrayList<>();
private Context context;
private View btnConfirm, btnNavUp, btnCancel;
private LayoutDirectoryChooserBinding binding;
private FileObserver fileObserver;
private File selectedDir;
private String initialDirectory;
private TextView tvSelectedFolder;
private FileObserver fileObserver;
private SimpleAdapter<String> listDirectoriesAdapter;
private OnFragmentInteractionListener interactionListener;
private boolean showZaAiConfigFiles = false;
private boolean showBackupFiles = false;
private View.OnClickListener navigationOnClickListener;
private FileListViewModel fileListViewModel;
private OnCancelListener onCancelListener;
public DirectoryChooser() {
super();
@ -51,8 +62,8 @@ public final class DirectoryChooser extends DialogFragment {
return this;
}
public DirectoryChooser setShowZaAiConfigFiles(final boolean showZaAiConfigFiles) {
this.showZaAiConfigFiles = showZaAiConfigFiles;
public DirectoryChooser setShowBackupFiles(final boolean showBackupFiles) {
this.showBackupFiles = showBackupFiles;
return this;
}
@ -74,60 +85,71 @@ public final class DirectoryChooser extends DialogFragment {
@NonNull
@Override
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;
if (context == null) context = getContext();
if (context == null) context = getActivity();
if (context == null) context = inflater.getContext();
final View view = inflater.inflate(R.layout.layout_directory_chooser, container, false);
btnNavUp = view.findViewById(R.id.btnNavUp);
btnCancel = view.findViewById(R.id.btnCancel);
btnConfirm = view.findViewById(R.id.btnConfirm);
tvSelectedFolder = view.findViewById(R.id.txtvSelectedFolder);
if (context == null) return;
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
final String text = "Storage permissions denied!";
if (container == null) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
} else {
Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
}
dismiss();
}
final View.OnClickListener clickListener = v -> {
final Object tag;
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 (v == binding.btnConfirm) {
if (interactionListener != null && isValidFile(selectedDir))
interactionListener.onSelectDirectory(selectedDir.getAbsolutePath());
interactionListener.onSelectDirectory(selectedDir);
dismiss();
} else if (v == btnCancel) {
} else if (v == binding.btnCancel) {
if (onCancelListener != null) {
onCancelListener.onCancel();
}
dismiss();
}
};
btnNavUp.setOnClickListener(clickListener);
btnCancel.setOnClickListener(clickListener);
btnConfirm.setOnClickListener(clickListener);
listDirectoriesAdapter = new SimpleAdapter<>(context, fileNames, clickListener);
final RecyclerView directoriesList = view.findViewById(R.id.directoryList);
directoriesList.setLayoutManager(new LinearLayoutManager(context));
directoriesList.setAdapter(listDirectoriesAdapter);
navigationOnClickListener = v -> {
final File parent;
if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) {
changeDirectory(parent);
}
};
binding.toolbar.setNavigationOnClickListener(navigationOnClickListener);
binding.toolbar.setSubtitle(showBackupFiles ? R.string.select_backup_file : R.string.select_folder);
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 initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory();
changeDirectory(initialDir);
return view;
}
@Override
@ -153,10 +175,14 @@ public final class DirectoryChooser extends DialogFragment {
public void onBackPressed() {
if (selectedDir != null) {
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();
else
} else {
changeDirectory(selectedDir.getParentFile());
}
}
}
};
@ -189,21 +215,28 @@ public final class DirectoryChooser extends DialogFragment {
private void changeDirectory(final File dir) {
if (dir != null && dir.isDirectory()) {
final String path = dir.getAbsolutePath();
binding.toolbar.setTitle(path);
final File[] contents = dir.listFiles();
if (contents != null) {
fileNames.clear();
final List<File> fileNames = new ArrayList<>();
for (final File f : contents) {
final String name = f.getName();
if (f.isDirectory() || showZaAiConfigFiles && f.isFile() && name.toLowerCase().endsWith(".zaai"))
fileNames.add(name);
final String nameLowerCase = name.toLowerCase();
final boolean isBackupFile = nameLowerCase.endsWith(".zaai") || nameLowerCase.endsWith(".backup");
if (f.isDirectory() || (showBackupFiles && f.isFile() && isBackupFile))
fileNames.add(f);
}
Collections.sort(fileNames);
Collections.sort(fileNames, (o1, o2) -> {
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;
tvSelectedFolder.setText(path);
listDirectoriesAdapter.notifyDataSetChanged();
fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) {
private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir);
@ -222,15 +255,15 @@ public final class DirectoryChooser extends DialogFragment {
if (selectedDir != null) {
final String path = selectedDir.getAbsolutePath();
toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile);
btnConfirm.setEnabled(isValidFile(selectedDir));
binding.btnConfirm.setEnabled(isValidFile(selectedDir));
}
}
private void toggleUpButton(final boolean enable) {
if (btnNavUp != null) {
btnNavUp.setEnabled(enable);
btnNavUp.setAlpha(enable ? 1f : 0.617f);
}
binding.toolbar.setNavigationOnClickListener(enable ? navigationOnClickListener : null);
final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
if (navigationIcon == null) return;
navigationIcon.setAlpha(enable ? 255 : (int) (255 * 0.617));
}
private boolean isValidFile(final File file) {
@ -242,7 +275,17 @@ public final class DirectoryChooser extends DialogFragment {
return this;
}
public void setOnCancelListener(final OnCancelListener onCancelListener) {
if (onCancelListener != null) {
this.onCancelListener = onCancelListener;
}
}
public interface OnCancelListener {
void onCancel();
}
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 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) {
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);

View File

@ -1,40 +1,40 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.text.InputFilter;
import android.text.InputType;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import android.widget.Toast;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatEditText;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.List;
import java.util.Map;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awaisomereport.LogCollector.LogFile;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class ExportImportUtils {
private static final String TAG = "ExportImportUtils";
public static final int FLAG_COOKIES = 1;
public static final int FLAG_FAVORITES = 1 << 1;
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)
@interface ExportImportFlags {}
public static void Export(@Nullable final String password, @ExportImportFlags final int flags, @NonNull final File filePath,
final FetchListener<Boolean> fetchListener) {
final String exportString = ExportImportUtils.getExportString(flags);
if (!TextUtils.isEmpty(exportString)) {
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
public static void exportData(@Nullable final String password,
@ExportImportFlags final int flags,
@NonNull final File filePath,
final FetchListener<Boolean> fetchListener,
@NonNull final Context context) {
final String exportString = getExportString(flags, context);
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
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("AWAISKING_APP", "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
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,
final FetchListener<Boolean> fetchListener) {
try (final FileInputStream fis = new FileInputStream(filePath)) {
public static void importData(@NonNull final Context context,
@ExportImportFlags final int flags,
@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 StringBuilder builder = new StringBuilder();
int c;
while ((c = fis.read()) != -1) {
builder.append((char) c);
}
if (configType == 'A') {
// password
final AppCompatEditText editText = new AppCompatEditText(context);
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(32)});
editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
new AlertDialog.Builder(context).setView(editText).setTitle(R.string.password)
.setPositiveButton(R.string.confirm, (dialog, which) -> {
final CharSequence text = editText.getText();
if (!TextUtils.isEmpty(text)) {
try {
final byte[] passwordBytes = text.toString().getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
saveToSettings(new String(PasswordUtils.dec(builder.toString(), bytes)), flags,
fetchListener);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
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();
if (TextUtils.isEmpty(password)) return;
try {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
importJson(new String(PasswordUtils.dec(builder.toString(), bytes)),
flags,
fetchListener);
} catch (final IncorrectPasswordException e) {
throw e;
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e);
}
} else if (configType == 'Z') {
saveToSettings(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
flags, fetchListener);
importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
flags,
fetchListener);
} 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);
}
} catch (IncorrectPasswordException e) {
// separately handle incorrect password
throw e;
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
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 {
final JSONObject jsonObject = new JSONObject(json);
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) {
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);
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);
}
}
importSettings(jsonObject);
}
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) {
final JSONArray cookies = jsonObject.getJSONArray("cookies");
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());
}
importAccounts(jsonObject);
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) {
final JSONArray favs = jsonObject.getJSONArray("favs");
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")));
}
importFavorites(jsonObject);
}
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson");
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
private static String getExportString(@ExportImportFlags final int flags) {
private static String getExportString(@ExportImportFlags final int flags,
@NonNull final Context context) {
String result = null;
try {
final JSONObject jsonObject = new JSONObject();
String str;
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
str = getSettings();
if (str != null) jsonObject.put("settings", new JSONObject(str));
jsonObject.put("settings", getSettings(context));
}
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
str = getCookies();
if (str != null) jsonObject.put("cookies", new JSONArray(str));
jsonObject.put("cookies", getCookies());
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
str = getFavorites();
if (str != null) jsonObject.put("favs", new JSONArray(str));
jsonObject.put("favs", getFavorites());
}
result = jsonObject.toString();
} catch (final Exception e) {
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;
}
@Nullable
private static String getSettings() {
String result = null;
@NonNull
private static JSONObject getSettings(@NonNull final Context context) {
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) {
try {
final JSONObject json = new JSONObject();
json.put(Constants.APP_THEME, settingsHelper.getString(Constants.APP_THEME));
json.put(Constants.APP_LANGUAGE, settingsHelper.getString(Constants.APP_LANGUAGE));
String str = settingsHelper.getString(Constants.FOLDER_PATH);
if (!TextUtils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str);
str = settingsHelper.getString(Constants.DATE_TIME_FORMAT);
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_FORMAT, str);
str = settingsHelper.getString(Constants.DATE_TIME_SELECTION);
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_SELECTION, str);
str = settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT);
if (!TextUtils.isEmpty(str)) json.put(Constants.CUSTOM_DATE_TIME_FORMAT, str);
json.put(Constants.DOWNLOAD_USER_FOLDER, settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER));
json.put(Constants.MUTED_VIDEOS, settingsHelper.getBoolean(Constants.MUTED_VIDEOS));
json.put(Constants.AUTOPLAY_VIDEOS, settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
json.put(Constants.AUTOLOAD_POSTS, settingsHelper.getBoolean(Constants.AUTOLOAD_POSTS));
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);
@NonNull
private static JSONArray getFavorites() {
if (Utils.dataBox == null) return new JSONArray();
try {
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final JSONArray jsonArray = new JSONArray();
for (final DataBox.FavoriteModel favorite : allFavorites) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery());
jsonObject.put("type", favorite.getType().toString());
jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("pic_url", favorite.getPicUrl());
jsonObject.put("d", favorite.getDateAdded().getTime());
jsonArray.put(jsonObject);
}
return jsonArray;
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting favorites", e);
}
}
return result;
return new JSONArray();
}
@Nullable
private static String getFavorites() {
String result = null;
if (Utils.dataBox != null) {
try {
final ArrayList<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final int allFavoritesSize;
if (allFavorites != null && (allFavoritesSize = allFavorites.size()) > 0) {
final JSONArray jsonArray = new JSONArray();
for (int i = 0; i < allFavoritesSize; i++) {
final DataBox.FavoriteModel favorite = allFavorites.get(i);
final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery());
jsonObject.put("d", favorite.getDate());
jsonObject.put("s", favorite.getDisplayName());
jsonArray.put(jsonObject);
}
result = jsonArray.toString();
}
} catch (final Exception e) {
result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
@NonNull
private static JSONArray getCookies() {
if (Utils.dataBox == null) return new JSONArray();
try {
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final JSONArray jsonArray = new JSONArray();
for (final DataBox.CookieModel cookie : allCookies) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookie.getUid());
jsonObject.put("u", cookie.getUsername());
jsonObject.put("c", cookie.getCookie());
jsonObject.put("full_name", cookie.getFullName());
jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject);
}
return jsonArray;
} catch (final Exception e) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting accounts", e);
}
}
return result;
}
@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);
}
return new JSONArray();
}
}

View File

@ -34,7 +34,7 @@ public class NavigationExtensions {
int firstFragmentGraphId = 0;
for (int i = 0; i < navGraphIds.size(); 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 NavController navController = navHostFragment.getNavController();
final int graphId = navController.getGraph().getId();
@ -57,7 +57,8 @@ public class NavigationExtensions {
return false;
}
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);
Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragment == null) {
@ -176,7 +177,7 @@ public class NavigationExtensions {
final Intent intent) {
for (int i = 0; i < navGraphIds.size(); 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);
if (navHostFragment.getNavController().handleDeepLink(intent)) {
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 threadTitle = data.getString("thread_title");
final String threadNewestCursor = data.getString("newest_cursor");
final String threadOldestCursor = data.getString("oldest_cursor");
final String threadNewestCursor = data.optString("newest_cursor");
final String threadOldestCursor = data.optString("oldest_cursor");
final String threadNextCursor = data.has("next_cursor") ? data.getString("next_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;
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

View File

@ -1,28 +1,20 @@
package awais.instagrabber.utils;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.text.Editable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.util.Pair;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.FragmentManager;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
@ -39,11 +31,9 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogImportExportBinding;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
public final class Utils {
private static final String TAG = "Utils";
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);
}
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) {
if (clipboardManager == null)
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
@ -89,100 +66,6 @@ public final class Utils {
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) {
final String signed = sign(new JSONObject(form).toString());
if (signed == null) {
@ -251,4 +134,16 @@ public final class Utils {
}
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,
final String username,
final boolean storiesig,
final boolean isLoc,
final boolean isHashtag,
final boolean highlight,
final ServiceCallback<List<StoryModel>> callback) {
final String url = buildUrl(id, storiesig, isLoc, isHashtag, highlight);
final String userAgent = storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT;
final Call<String> userStoryCall = repository.getUserStory(userAgent, url);
final String url = buildUrl(id, isLoc, isHashtag, highlight);
final Call<String> userStoryCall = repository.getUserStory(Constants.I_USER_AGENT, url);
userStoryCall.enqueue(new Callback<String>() {
@Override
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);
if (!storiesig && !highlight)
if (!highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
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 StringBuilder builder = new StringBuilder();
builder.append("https://");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
builder.append("https://i.instagram.com/api/v1/");
if (isLoc) {
builder.append("locations/");
}
@ -266,11 +258,7 @@ public class StoriesService extends BaseService {
}
builder.append(userId);
if (!highlight) {
if (storiesig) {
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
builder.append("/story/");
}
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">
<path
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>

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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/semi_transparent_black" />
<solid android:color="@color/black_a50" />
<padding
android:left="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_height="?attr/actionBarSize"
app:layout_collapseMode="none"
app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface"
app:title="@string/app_name"
tools:menu="@menu/main_menu" />
</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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/semi_transparent_black">
android:background="@color/black_a50">
<awais.instagrabber.customviews.drawee.ZoomableDraweeView
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"
app:layout_scrollFlags="scroll">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/tagInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space"
android:visibility="visible">
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage"
android:layout_width="@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
android:id="@+id/mainTagPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
app:layout_constraintBottom_toTopOf="@id/btnFollowTag"
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:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" />
</LinearLayout>
app:chipBackgroundColor="@null"
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.AppBarLayout>

View File

@ -17,101 +17,128 @@
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<RelativeLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/locInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@null"
android:orientation="vertical"
android:paddingBottom="5dp">
android:padding="8dp">
<LinearLayout
android:id="@+id/locInfo"
android:layout_width="match_parent"
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainLocationImage"
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:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
android:layout_marginStart="8dp"
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
android:id="@+id/mainLocationImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:background="?selectableItemBackgroundBorderless"
app:actualImageScaleType="fitCenter" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainLocPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
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>
<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"
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_star_plus_24"
app:chipIconTint="@color/yellow_800"
app:layout_constraintBottom_toBottomOf="@id/mainLocationImage"
app:layout_constraintStart_toEndOf="@id/btnMap"
app:layout_constraintTop_toBottomOf="@id/mainLocPostCount"
app:rippleColor="@color/yellow_400" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/locationFullName"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/locInfo"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
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" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationBiography"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/locationFullName"
android:background="?android:selectableItemBackground"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
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
android:id="@+id/locationUrl"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/locationBiography"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone"
tools:text="https://austinhuang.me/" />
</RelativeLayout>
app:layout_constraintBottom_toBottomOf="parent"
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.AppBarLayout>

View File

@ -79,8 +79,10 @@
android:ellipsize="marquee"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingTop="8dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold"
@ -102,15 +104,34 @@
app:srcCompat="@drawable/verified"
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
android:id="@+id/mainBiography"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="?android:selectableItemBackground"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/mainUrl"
app:layout_constraintEnd_toEndOf="parent"
@ -124,10 +145,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/mainBiography"
android:ellipsize="marquee"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
@ -135,97 +153,96 @@
app:layout_constraintTop_toBottomOf="@id/mainBiography"
tools:text="https://austinhuang.me/"
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"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textColor="@color/deep_purple_200"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toStartOf="@id/btnRestrict"
app:icon="@drawable/ic_outline_person_add_24"
app:iconGravity="top"
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_constraintTop_toBottomOf="@id/mainUrl"
app:rippleColor="@color/purple_200"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRestrict"
<com.google.android.material.button.MaterialButton
android:id="@+id/btnTagged"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/restrict"
android:textColor="@color/btn_orange_text_color"
android:text="@string/tagged"
android:textColor="@color/deep_orange_600"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
app:layout_constraintEnd_toStartOf="@id/btnBlock"
app:icon="@drawable/ic_outline_person_pin_24"
app:iconGravity="top"
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_constraintTop_toBottomOf="@id/mainUrl"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatButton
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
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaved"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/saved"
android:textColor="@color/btn_orange_text_color"
android:textColor="@color/blue_700"
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_constraintEnd_toStartOf="@id/btnLiked"
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" />
<androidx.appcompat.widget.AppCompatButton
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLiked"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color"
android:textColor="@color/red_600"
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_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/btnDM"
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" />
<androidx.constraintlayout.widget.Barrier

View File

@ -1,15 +1,31 @@
<?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"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
tools:viewBindingIgnore="true" />
android:background="?attr/selectableItemBackground"
android:minHeight="56dp">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
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:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/semi_transparent_black" />
tools:background="@color/black_a50" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="24dp"

View File

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

View File

@ -20,37 +20,6 @@
app:roundAsCircle="true"
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
android:id="@+id/barrier"
android:layout_width="wrap_content"
@ -105,29 +74,10 @@
android:paddingEnd="8dp"
android:paddingRight="8dp"
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_constraintTop_toBottomOf="@id/tvComment"
tools:text="sub-comment" />
<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" />
tools:text="sub-comment long long long long long long long long long long" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/preview_barrier"
@ -147,46 +97,22 @@
tools:placeholderImage="@mipmap/ic_launcher"
tools:visibility="visible" />
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/preview_pic1"-->
<!-- 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/preview_pic2"-->
<!-- app:layout_constraintStart_toEndOf="@id/preview_barrier"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<!--<com.facebook.drawee.view.SimpleDraweeView-->
<!-- android:id="@+id/preview_pic2"-->
<!-- android:layout_width="@dimen/simple_item_picture_size_exact_half"-->
<!-- android:layout_height="@dimen/simple_item_picture_size_exact_half"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/preview_pic1"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:placeholderImage="@mipmap/ic_launcher" />-->
<!--<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.appcompat.widget.AppCompatTextView
android:id="@+id/tvDate"
android:layout_width="0dp"
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/preview_barrier"
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
app:layout_constraintTop_toBottomOf="@id/tvSubComment"
tools:text="some long long long long long date" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -1,116 +1,65 @@
<?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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/footer"
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"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
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
android:id="@+id/btnNavUp"
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"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvSelectedFolderLabel"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
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>
app:navigationIcon="@drawable/ic_arrow_upward_24"
tools:title="/this/that/thy" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/directoryList"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_above="@id/footer"
android:layout_below="@id/directoryInfo" />
</RelativeLayout>
app:layout_constraintBottom_toTopOf="@id/bottom_horizontal_divider"
app:layout_constraintEnd_toEndOf="parent"
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_height="?actionBarSize"
android:padding="8dp"
android:src="@drawable/expired" />
app:srcCompat="@drawable/ic_clock_alert_outline_24" />
<FrameLayout
android:layout_width="match_parent"

View File

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

View File

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

View File

@ -34,6 +34,24 @@
app:nullable="true" />
</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
android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
@ -47,6 +65,12 @@
<action
android:id="@+id/action_morePreferencesFragment_to_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
android:id="@+id/settingsPreferencesFragment"
@ -69,4 +93,12 @@
android:id="@+id/themePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.ThemePreferencesFragment"
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>

View File

@ -36,7 +36,7 @@
<action
android:id="@+id/action_global_profileFragment"
app:destination="@id/profileFragment">
app:destination="@id/profile_nav_graph">
<argument
android:name="username"
android:defaultValue=""
@ -74,6 +74,9 @@
<action
android:id="@+id/action_profileFragment_to_storyViewerFragment"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_profileFragment_to_dMThreadFragment"
app:destination="@id/directMessagesThreadFragment" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
@ -145,4 +148,31 @@
app:argType="string"
app:nullable="true" />
</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>

View File

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

View File

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

View File

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

View File

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

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