Account profiles no longer partial!
This commit is contained in:
parent
60d68b0ae6
commit
1429dfc7b5
25 changed files with 896 additions and 87 deletions
|
@ -28,6 +28,6 @@ dependencies {
|
||||||
compile 'com.android.support:appcompat-v7:25.1.0'
|
compile 'com.android.support:appcompat-v7:25.1.0'
|
||||||
compile 'com.android.support:recyclerview-v7:25.1.0'
|
compile 'com.android.support:recyclerview-v7:25.1.0'
|
||||||
compile 'com.android.volley:volley:1.0.0'
|
compile 'com.android.volley:volley:1.0.0'
|
||||||
testCompile 'junit:junit:4.12'
|
|
||||||
compile 'com.android.support:design:25.1.0'
|
compile 'com.android.support:design:25.1.0'
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<activity android:name=".ViewTagActivity" />
|
<activity android:name=".ViewTagActivity" />
|
||||||
<activity android:name=".AccountActivity" />
|
<activity android:name=".AccountActivity" />
|
||||||
<service
|
<service
|
||||||
android:name=".NotificationService"
|
android:name=".PullNotificationService"
|
||||||
android:description="@string/notification_service_description"
|
android:description="@string/notification_service_description"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
76
app/src/main/java/com/keylesspalace/tusky/Account.java
Normal file
76
app/src/main/java/com/keylesspalace/tusky/Account.java
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.text.Spanned;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Account {
|
||||||
|
public String id;
|
||||||
|
public String username;
|
||||||
|
public String displayName;
|
||||||
|
public Spanned note;
|
||||||
|
public String url;
|
||||||
|
public String avatar;
|
||||||
|
public String header;
|
||||||
|
public String followersCount;
|
||||||
|
public String followingCount;
|
||||||
|
public String statusesCount;
|
||||||
|
|
||||||
|
public static Account parse(JSONObject object) throws JSONException {
|
||||||
|
Account account = new Account();
|
||||||
|
account.id = object.getString("id");
|
||||||
|
account.username = object.getString("acct");
|
||||||
|
account.displayName = object.getString("display_name");
|
||||||
|
if (account.displayName.isEmpty()) {
|
||||||
|
account.displayName = object.getString("username");
|
||||||
|
}
|
||||||
|
account.note = HtmlUtils.fromHtml(object.getString("note"));
|
||||||
|
account.url = object.getString("url");
|
||||||
|
String avatarUrl = object.getString("avatar");
|
||||||
|
if (!avatarUrl.equals("/avatars/original/missing.png")) {
|
||||||
|
account.avatar = avatarUrl;
|
||||||
|
} else {
|
||||||
|
account.avatar = "";
|
||||||
|
}
|
||||||
|
String headerUrl = object.getString("header");
|
||||||
|
if (!headerUrl.equals("/headers/original/missing.png")) {
|
||||||
|
account.header = headerUrl;
|
||||||
|
} else {
|
||||||
|
account.header = "";
|
||||||
|
}
|
||||||
|
account.followersCount = object.getString("followers_count");
|
||||||
|
account.followingCount = object.getString("following_count");
|
||||||
|
account.statusesCount = object.getString("statuses_count");
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Account> parse(JSONArray array) throws JSONException {
|
||||||
|
List<Account> accounts = new ArrayList<>();
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
JSONObject object = array.getJSONObject(i);
|
||||||
|
Account account = parse(object);
|
||||||
|
accounts.add(account);
|
||||||
|
}
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
public interface AccountActionListener {
|
||||||
|
void onViewAccount(String id);
|
||||||
|
}
|
|
@ -18,16 +18,20 @@ package com.keylesspalace.tusky;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.volley.AuthFailureError;
|
import com.android.volley.AuthFailureError;
|
||||||
|
@ -47,10 +51,16 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class AccountActivity extends AppCompatActivity {
|
public class AccountActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = "AccountActivity"; // logging tag
|
||||||
|
|
||||||
private String domain;
|
private String domain;
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
|
private String accountId;
|
||||||
private boolean following = false;
|
private boolean following = false;
|
||||||
private boolean blocking = false;
|
private boolean blocking = false;
|
||||||
|
private boolean isSelf = false;
|
||||||
|
private String openInWebUrl;
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -58,17 +68,13 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
setContentView(R.layout.activity_account);
|
setContentView(R.layout.activity_account);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String username = intent.getStringExtra("username");
|
accountId = intent.getStringExtra("id");
|
||||||
String id = intent.getStringExtra("id");
|
|
||||||
TextView accountName = (TextView) findViewById(R.id.account_username);
|
|
||||||
accountName.setText(username);
|
|
||||||
|
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
domain = preferences.getString("domain", null);
|
domain = preferences.getString("domain", null);
|
||||||
accessToken = preferences.getString("accessToken", null);
|
accessToken = preferences.getString("accessToken", null);
|
||||||
assert(domain != null);
|
String loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||||
assert(accessToken != null);
|
|
||||||
|
|
||||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
|
@ -79,35 +85,50 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
avatar.setErrorImageResId(R.drawable.avatar_error);
|
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||||
header.setDefaultImageResId(R.drawable.account_header_default);
|
header.setDefaultImageResId(R.drawable.account_header_default);
|
||||||
|
|
||||||
obtainAccount(id);
|
obtainAccount();
|
||||||
obtainRelationships(id);
|
if (!accountId.equals(loggedInAccountId)) {
|
||||||
|
obtainRelationships();
|
||||||
|
} else {
|
||||||
|
/* Cause the options menu to update and instead show an options menu for when the
|
||||||
|
* account being shown is their own account. */
|
||||||
|
isSelf = true;
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the tabs and timeline pager.
|
// Setup the tabs and timeline pager.
|
||||||
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(), id);
|
AccountPagerAdapter adapter = new AccountPagerAdapter(
|
||||||
|
getSupportFragmentManager(), this, accountId);
|
||||||
String[] pageTitles = {
|
String[] pageTitles = {
|
||||||
getString(R.string.title_statuses),
|
getString(R.string.title_statuses),
|
||||||
getString(R.string.title_follows),
|
getString(R.string.title_follows),
|
||||||
getString(R.string.title_followers)
|
getString(R.string.title_followers)
|
||||||
};
|
};
|
||||||
adapter.setPageTitles(pageTitles);
|
adapter.setPageTitles(pageTitles);
|
||||||
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||||
viewPager.setAdapter(adapter);
|
viewPager.setAdapter(adapter);
|
||||||
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
|
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
for (int i = 0; i < tabLayout.getTabCount(); i++) {
|
||||||
|
TabLayout.Tab tab = tabLayout.getTabAt(i);
|
||||||
|
tab.setCustomView(adapter.getTabView(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void obtainAccount(String id) {
|
private void obtainAccount() {
|
||||||
String endpoint = String.format(getString(R.string.endpoint_accounts), id);
|
String endpoint = String.format(getString(R.string.endpoint_accounts), accountId);
|
||||||
String url = "https://" + domain + endpoint;
|
String url = "https://" + domain + endpoint;
|
||||||
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
||||||
new Response.Listener<JSONObject>() {
|
new Response.Listener<JSONObject>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(JSONObject response) {
|
public void onResponse(JSONObject response) {
|
||||||
|
Account account;
|
||||||
try {
|
try {
|
||||||
onObtainAccountSuccess(response);
|
account = Account.parse(response);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
onObtainAccountFailure();
|
onObtainAccountFailure();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
onObtainAccountSuccess(account);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Response.ErrorListener() {
|
new Response.ErrorListener() {
|
||||||
|
@ -126,7 +147,7 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onObtainAccountSuccess(JSONObject response) throws JSONException {
|
private void onObtainAccountSuccess(Account account) {
|
||||||
TextView username = (TextView) findViewById(R.id.account_username);
|
TextView username = (TextView) findViewById(R.id.account_username);
|
||||||
TextView displayName = (TextView) findViewById(R.id.account_display_name);
|
TextView displayName = (TextView) findViewById(R.id.account_display_name);
|
||||||
TextView note = (TextView) findViewById(R.id.account_note);
|
TextView note = (TextView) findViewById(R.id.account_note);
|
||||||
|
@ -134,36 +155,52 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
||||||
|
|
||||||
String usernameFormatted = String.format(
|
String usernameFormatted = String.format(
|
||||||
getString(R.string.status_username_format), response.getString("acct"));
|
getString(R.string.status_username_format), account.username);
|
||||||
username.setText(usernameFormatted);
|
username.setText(usernameFormatted);
|
||||||
|
|
||||||
String displayNameString = response.getString("display_name");
|
displayName.setText(account.displayName);
|
||||||
displayName.setText(displayNameString);
|
|
||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
actionBar.setTitle(displayNameString);
|
actionBar.setTitle(account.displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
String noteHtml = response.getString("note");
|
note.setText(account.note);
|
||||||
Spanned noteSpanned = HtmlUtils.fromHtml(noteHtml);
|
note.setLinksClickable(true);
|
||||||
note.setText(noteSpanned);
|
|
||||||
|
|
||||||
ImageLoader imageLoader = VolleySingleton.getInstance(this).getImageLoader();
|
ImageLoader imageLoader = VolleySingleton.getInstance(this).getImageLoader();
|
||||||
avatar.setImageUrl(response.getString("avatar"), imageLoader);
|
if (!account.avatar.isEmpty()) {
|
||||||
String headerUrl = response.getString("header");
|
avatar.setImageUrl(account.avatar, imageLoader);
|
||||||
if (!headerUrl.isEmpty() && !headerUrl.equals("/headers/original/missing.png")) {
|
}
|
||||||
header.setImageUrl(headerUrl, imageLoader);
|
if (!account.header.isEmpty()) {
|
||||||
|
header.setImageUrl(account.header, imageLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
openInWebUrl = account.url;
|
||||||
|
|
||||||
|
// Add counts to the tabs in the TabLayout.
|
||||||
|
String[] counts = {
|
||||||
|
account.statusesCount,
|
||||||
|
account.followingCount,
|
||||||
|
account.followersCount,
|
||||||
|
};
|
||||||
|
for (int i = 0; i < tabLayout.getTabCount(); i++) {
|
||||||
|
TabLayout.Tab tab = tabLayout.getTabAt(i);
|
||||||
|
if (tab != null) {
|
||||||
|
View view = tab.getCustomView();
|
||||||
|
TextView total = (TextView) view.findViewById(R.id.total);
|
||||||
|
total.setText(counts[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onObtainAccountFailure() {
|
private void onObtainAccountFailure() {
|
||||||
//TODO: help
|
//TODO: help
|
||||||
assert(false);
|
Log.e(TAG, "Failed to obtain that account.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void obtainRelationships(String id) {
|
private void obtainRelationships() {
|
||||||
String endpoint = getString(R.string.endpoint_relationships);
|
String endpoint = getString(R.string.endpoint_relationships);
|
||||||
String url = String.format("https://%s%s?id=%s", domain, endpoint, id);
|
String url = String.format("https://%s%s?id=%s", domain, endpoint, accountId);
|
||||||
JsonArrayRequest request = new JsonArrayRequest(url,
|
JsonArrayRequest request = new JsonArrayRequest(url,
|
||||||
new Response.Listener<JSONArray>() {
|
new Response.Listener<JSONArray>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -207,7 +244,7 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private void onObtainRelationshipsFailure() {
|
private void onObtainRelationshipsFailure() {
|
||||||
//TODO: help
|
//TODO: help
|
||||||
assert(false);
|
Log.e(TAG, "Could not obtain relationships?");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -218,30 +255,139 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
MenuItem follow = menu.findItem(R.id.action_follow);
|
if (!isSelf) {
|
||||||
String title;
|
MenuItem follow = menu.findItem(R.id.action_follow);
|
||||||
if (following) {
|
String title;
|
||||||
title = getString(R.string.action_unfollow);
|
if (following) {
|
||||||
|
title = getString(R.string.action_unfollow);
|
||||||
|
} else {
|
||||||
|
title = getString(R.string.action_follow);
|
||||||
|
}
|
||||||
|
follow.setTitle(title);
|
||||||
|
MenuItem block = menu.findItem(R.id.action_block);
|
||||||
|
if (blocking) {
|
||||||
|
title = getString(R.string.action_unblock);
|
||||||
|
} else {
|
||||||
|
title = getString(R.string.action_block);
|
||||||
|
}
|
||||||
|
block.setTitle(title);
|
||||||
} else {
|
} else {
|
||||||
title = getString(R.string.action_follow);
|
// It shouldn't be possible to block or follow yourself.
|
||||||
|
menu.removeItem(R.id.action_follow);
|
||||||
|
menu.removeItem(R.id.action_block);
|
||||||
}
|
}
|
||||||
follow.setTitle(title);
|
|
||||||
MenuItem block = menu.findItem(R.id.action_block);
|
|
||||||
if (blocking) {
|
|
||||||
title = getString(R.string.action_unblock);
|
|
||||||
} else {
|
|
||||||
title = getString(R.string.action_block);
|
|
||||||
}
|
|
||||||
block.setTitle(title);
|
|
||||||
return super.onPrepareOptionsMenu(menu);
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void follow() {
|
private void postRequest(String endpoint, Response.Listener<JSONObject> listener,
|
||||||
|
Response.ErrorListener errorListener) {
|
||||||
|
String url = "https://" + domain + endpoint;
|
||||||
|
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, null, listener,
|
||||||
|
errorListener) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void block() {
|
private void follow(final String id) {
|
||||||
|
int endpointId;
|
||||||
|
if (following) {
|
||||||
|
endpointId = R.string.endpoint_unfollow;
|
||||||
|
} else {
|
||||||
|
endpointId = R.string.endpoint_follow;
|
||||||
|
}
|
||||||
|
postRequest(String.format(getString(endpointId), id),
|
||||||
|
new Response.Listener<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONObject response) {
|
||||||
|
boolean followingValue;
|
||||||
|
try {
|
||||||
|
followingValue = response.getBoolean("following");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onFollowFailure(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
following = followingValue;
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onFollowFailure(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFollowFailure(final String id) {
|
||||||
|
int messageId;
|
||||||
|
if (following) {
|
||||||
|
messageId = R.string.error_unfollowing;
|
||||||
|
} else {
|
||||||
|
messageId = R.string.error_following;
|
||||||
|
}
|
||||||
|
View.OnClickListener listener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
follow(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Snackbar.make(findViewById(R.id.activity_account), messageId, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_retry, listener)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void block(final String id) {
|
||||||
|
int endpointId;
|
||||||
|
if (blocking) {
|
||||||
|
endpointId = R.string.endpoint_unblock;
|
||||||
|
} else {
|
||||||
|
endpointId = R.string.endpoint_block;
|
||||||
|
}
|
||||||
|
postRequest(String.format(getString(endpointId), id),
|
||||||
|
new Response.Listener<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONObject response) {
|
||||||
|
boolean blockingValue;
|
||||||
|
try {
|
||||||
|
blockingValue = response.getBoolean("blocking");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onBlockFailure(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blocking = blockingValue;
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onBlockFailure(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBlockFailure(final String id) {
|
||||||
|
int messageId;
|
||||||
|
if (blocking) {
|
||||||
|
messageId = R.string.error_unblocking;
|
||||||
|
} else {
|
||||||
|
messageId = R.string.error_blocking;
|
||||||
|
}
|
||||||
|
View.OnClickListener listener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
block(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Snackbar.make(findViewById(R.id.activity_account), messageId, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_retry, listener)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -253,12 +399,18 @@ public class AccountActivity extends AppCompatActivity {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case R.id.action_open_in_web: {
|
||||||
|
Uri uri = Uri.parse(openInWebUrl);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case R.id.action_follow: {
|
case R.id.action_follow: {
|
||||||
follow();
|
follow(accountId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case R.id.action_block: {
|
case R.id.action_block: {
|
||||||
block();
|
block(accountId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
app/src/main/java/com/keylesspalace/tusky/AccountAdapter.java
Normal file
158
app/src/main/java/com/keylesspalace/tusky/AccountAdapter.java
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is part of Tusky.
|
||||||
|
*
|
||||||
|
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||||
|
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.android.volley.toolbox.ImageLoader;
|
||||||
|
import com.android.volley.toolbox.NetworkImageView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AccountAdapter extends RecyclerView.Adapter {
|
||||||
|
private static final int VIEW_TYPE_ACCOUNT = 0;
|
||||||
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
private List<Account> accounts;
|
||||||
|
private AccountActionListener accountActionListener;
|
||||||
|
private FooterActionListener footerActionListener;
|
||||||
|
|
||||||
|
public AccountAdapter(AccountActionListener accountActionListener,
|
||||||
|
FooterActionListener footerActionListener) {
|
||||||
|
super();
|
||||||
|
accounts = new ArrayList<>();
|
||||||
|
this.accountActionListener = accountActionListener;
|
||||||
|
this.footerActionListener = footerActionListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
switch (viewType) {
|
||||||
|
default:
|
||||||
|
case VIEW_TYPE_ACCOUNT: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_account, parent, false);
|
||||||
|
return new AccountViewHolder(view);
|
||||||
|
}
|
||||||
|
case VIEW_TYPE_FOOTER: {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_footer, parent, false);
|
||||||
|
return new FooterViewHolder(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
|
if (position < accounts.size()) {
|
||||||
|
AccountViewHolder holder = (AccountViewHolder) viewHolder;
|
||||||
|
holder.setupWithAccount(accounts.get(position));
|
||||||
|
holder.setupActionListener(accountActionListener);
|
||||||
|
} else {
|
||||||
|
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||||
|
holder.setupButton(footerActionListener);
|
||||||
|
holder.setRetryMessage(R.string.footer_retry_accounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return accounts.size() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position == accounts.size()) {
|
||||||
|
return VIEW_TYPE_FOOTER;
|
||||||
|
} else {
|
||||||
|
return VIEW_TYPE_ACCOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(List<Account> newAccounts) {
|
||||||
|
if (accounts == null || accounts.isEmpty()) {
|
||||||
|
accounts = newAccounts;
|
||||||
|
} else {
|
||||||
|
int index = newAccounts.indexOf(accounts.get(0));
|
||||||
|
if (index == -1) {
|
||||||
|
accounts.addAll(0, newAccounts);
|
||||||
|
} else {
|
||||||
|
accounts.addAll(0, newAccounts.subList(0, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItems(List<Account> newAccounts) {
|
||||||
|
int end = accounts.size();
|
||||||
|
accounts.addAll(newAccounts);
|
||||||
|
notifyItemRangeInserted(end, newAccounts.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getItem(int position) {
|
||||||
|
if (position >= 0 && position < accounts.size()) {
|
||||||
|
return accounts.get(position);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AccountViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private View container;
|
||||||
|
private TextView username;
|
||||||
|
private TextView displayName;
|
||||||
|
private TextView note;
|
||||||
|
private NetworkImageView avatar;
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
public AccountViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
container = itemView.findViewById(R.id.account_container);
|
||||||
|
username = (TextView) itemView.findViewById(R.id.account_username);
|
||||||
|
displayName = (TextView) itemView.findViewById(R.id.account_display_name);
|
||||||
|
note = (TextView) itemView.findViewById(R.id.account_note);
|
||||||
|
avatar = (NetworkImageView) itemView.findViewById(R.id.account_avatar);
|
||||||
|
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||||
|
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupWithAccount(Account account) {
|
||||||
|
id = account.id;
|
||||||
|
String format = username.getContext().getString(R.string.status_username_format);
|
||||||
|
String formattedUsername = String.format(format, account.username);
|
||||||
|
username.setText(formattedUsername);
|
||||||
|
displayName.setText(account.displayName);
|
||||||
|
note.setText(account.note);
|
||||||
|
Context context = avatar.getContext();
|
||||||
|
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||||
|
avatar.setImageUrl(account.avatar, imageLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupActionListener(final AccountActionListener listener) {
|
||||||
|
container.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onViewAccount(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,31 +15,224 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.Contacts;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.DividerItemDecoration;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
public class AccountFragment extends Fragment {
|
import com.android.volley.AuthFailureError;
|
||||||
|
import com.android.volley.Response;
|
||||||
|
import com.android.volley.VolleyError;
|
||||||
|
import com.android.volley.toolbox.JsonArrayRequest;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AccountFragment extends Fragment implements AccountActionListener,
|
||||||
|
FooterActionListener {
|
||||||
public enum Type {
|
public enum Type {
|
||||||
FOLLOWS,
|
FOLLOWS,
|
||||||
FOLLOWERS,
|
FOLLOWERS,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccountFragment newInstance(Type type) {
|
private Type type;
|
||||||
|
private String accountId;
|
||||||
|
private String domain;
|
||||||
|
private String accessToken;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private LinearLayoutManager layoutManager;
|
||||||
|
private EndlessOnScrollListener scrollListener;
|
||||||
|
private AccountAdapter adapter;
|
||||||
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
|
|
||||||
|
public static AccountFragment newInstance(Type type, String accountId) {
|
||||||
Bundle arguments = new Bundle();
|
Bundle arguments = new Bundle();
|
||||||
AccountFragment fragment = new AccountFragment();
|
AccountFragment fragment = new AccountFragment();
|
||||||
arguments.putString("type", type.name());
|
arguments.putString("type", type.name());
|
||||||
|
arguments.putString("accountId", accountId);
|
||||||
fragment.setArguments(arguments);
|
fragment.setArguments(arguments);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
type = Type.valueOf(arguments.getString("type"));
|
||||||
|
accountId = arguments.getString("accountId");
|
||||||
|
|
||||||
|
SharedPreferences preferences = getContext().getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
domain = preferences.getString("domain", null);
|
||||||
|
accessToken = preferences.getString("accessToken", null);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
return super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_account, container, false);
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||||
|
recyclerView.setHasFixedSize(true);
|
||||||
|
layoutManager = new LinearLayoutManager(context);
|
||||||
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
DividerItemDecoration divider = new DividerItemDecoration(
|
||||||
|
context, layoutManager.getOrientation());
|
||||||
|
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.status_divider);
|
||||||
|
divider.setDrawable(drawable);
|
||||||
|
recyclerView.addItemDecoration(divider);
|
||||||
|
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||||
|
@Override
|
||||||
|
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
||||||
|
AccountAdapter adapter = (AccountAdapter) view.getAdapter();
|
||||||
|
Account account = adapter.getItem(adapter.getItemCount() - 2);
|
||||||
|
if (account != null) {
|
||||||
|
fetchAccounts(account.id);
|
||||||
|
} else {
|
||||||
|
fetchAccounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
recyclerView.addOnScrollListener(scrollListener);
|
||||||
|
adapter = new AccountAdapter(this, this);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
|
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
jumpToTop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
layout.addOnTabSelectedListener(onTabSelectedListener);
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAccounts(final String fromId) {
|
||||||
|
int endpointId;
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
case FOLLOWS: {
|
||||||
|
endpointId = R.string.endpoint_following;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FOLLOWERS: {
|
||||||
|
endpointId = R.string.endpoint_followers;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String endpoint = String.format(getString(endpointId), accountId);
|
||||||
|
String url = "https://" + domain + endpoint;
|
||||||
|
if (fromId != null) {
|
||||||
|
url += "?max_id=" + fromId;
|
||||||
|
}
|
||||||
|
JsonArrayRequest request = new JsonArrayRequest(url,
|
||||||
|
new Response.Listener<JSONArray>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONArray response) {
|
||||||
|
List<Account> accounts;
|
||||||
|
try {
|
||||||
|
accounts = Account.parse(response);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onFetchAccountsFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onFetchAccountsSuccess(accounts, fromId != null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onFetchAccountsFailure();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(getContext()).addToRequestQueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAccounts() {
|
||||||
|
fetchAccounts(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchAccountsSuccess(List<Account> accounts, boolean added) {
|
||||||
|
if (added) {
|
||||||
|
adapter.addItems(accounts);
|
||||||
|
} else {
|
||||||
|
adapter.update(accounts);
|
||||||
|
}
|
||||||
|
showFetchAccountsRetry(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFetchAccountsFailure() {
|
||||||
|
showFetchAccountsRetry(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFetchAccountsRetry(boolean show) {
|
||||||
|
RecyclerView.ViewHolder viewHolder =
|
||||||
|
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
|
||||||
|
if (viewHolder != null) {
|
||||||
|
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||||
|
holder.showRetry(show);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLoadMore() {
|
||||||
|
Account account = adapter.getItem(adapter.getItemCount() - 2);
|
||||||
|
if (account != null) {
|
||||||
|
fetchAccounts(account.id);
|
||||||
|
} else {
|
||||||
|
fetchAccounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onViewAccount(String id) {
|
||||||
|
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||||
|
intent.putExtra("id", id);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jumpToTop() {
|
||||||
|
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||||
|
scrollListener.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,22 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class AccountPagerAdapter extends FragmentPagerAdapter {
|
public class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
private Context context;
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String[] pageTitles;
|
private String[] pageTitles;
|
||||||
|
|
||||||
public AccountPagerAdapter(FragmentManager manager, String accountId) {
|
public AccountPagerAdapter(FragmentManager manager, Context context, String accountId) {
|
||||||
super(manager);
|
super(manager);
|
||||||
|
this.context = context;
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,10 +45,10 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS);
|
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS, accountId);
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS);
|
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS, accountId);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return null;
|
return null;
|
||||||
|
@ -59,4 +65,11 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||||
public CharSequence getPageTitle(int position) {
|
public CharSequence getPageTitle(int position) {
|
||||||
return pageTitles[position];
|
return pageTitles[position];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public View getTabView(int position) {
|
||||||
|
View view = LayoutInflater.from(context).inflate(R.layout.tab_account, null);
|
||||||
|
TextView title = (TextView) view.findViewById(R.id.title);
|
||||||
|
title.setText(pageTitles[position]);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,18 @@ import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class FooterViewHolder extends RecyclerView.ViewHolder {
|
public class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||||
private LinearLayout retryBar;
|
private LinearLayout retryBar;
|
||||||
|
private TextView retryMessage;
|
||||||
private Button retry;
|
private Button retry;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
public FooterViewHolder(View itemView) {
|
public FooterViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
retryBar = (LinearLayout) itemView.findViewById(R.id.footer_retry_bar);
|
retryBar = (LinearLayout) itemView.findViewById(R.id.footer_retry_bar);
|
||||||
|
retryMessage = (TextView) itemView.findViewById(R.id.footer_retry_message);
|
||||||
retry = (Button) itemView.findViewById(R.id.footer_retry_button);
|
retry = (Button) itemView.findViewById(R.id.footer_retry_button);
|
||||||
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||||
progressBar.setIndeterminate(true);
|
progressBar.setIndeterminate(true);
|
||||||
|
@ -43,6 +46,10 @@ public class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRetryMessage(int messageId) {
|
||||||
|
retryMessage.setText(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
public void showRetry(boolean show) {
|
public void showRetry(boolean show) {
|
||||||
if (!show) {
|
if (!show) {
|
||||||
retryBar.setVisibility(View.GONE);
|
retryBar.setVisibility(View.GONE);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
private static final String TAG = "MainActivity"; // logging tag
|
||||||
|
|
||||||
private AlarmManager alarmManager;
|
private AlarmManager alarmManager;
|
||||||
private PendingIntent serviceAlarmIntent;
|
private PendingIntent serviceAlarmIntent;
|
||||||
private boolean notificationServiceEnabled;
|
private boolean notificationServiceEnabled;
|
||||||
|
@ -75,12 +78,12 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// Retrieve notification update preference.
|
// Retrieve notification update preference.
|
||||||
SharedPreferences preferences = getSharedPreferences(
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
notificationServiceEnabled = preferences.getBoolean("notificationService", true);
|
notificationServiceEnabled = preferences.getBoolean("notificationService", false);
|
||||||
long notificationCheckInterval =
|
long notificationCheckInterval =
|
||||||
preferences.getLong("notificationCheckInterval", 5 * 60 * 1000);
|
preferences.getLong("notificationCheckInterval", 5 * 60 * 1000);
|
||||||
// Start up the NotificationsService.
|
// Start up the PullNotificationsService.
|
||||||
alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
Intent intent = new Intent(this, NotificationService.class);
|
Intent intent = new Intent(this, PullNotificationService.class);
|
||||||
final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary.
|
final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary.
|
||||||
serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent,
|
serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE, intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
@ -90,6 +93,20 @@ public class MainActivity extends AppCompatActivity {
|
||||||
} else {
|
} else {
|
||||||
alarmManager.cancel(serviceAlarmIntent);
|
alarmManager.cancel(serviceAlarmIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @Unused: for Firebase Push Notifications
|
||||||
|
Log.d(TAG, "token " + FirebaseInstanceId.getInstance().getToken());
|
||||||
|
|
||||||
|
// Check if it's necessary to register for push notifications for this instance.
|
||||||
|
boolean registered = preferences.getBoolean("firebaseRegistered", false);
|
||||||
|
if (!registered) {
|
||||||
|
String registrationId = preferences.getString("firebaseRegistrationId", null);
|
||||||
|
if (registrationId == null) {
|
||||||
|
registrationId = FirebaseInstanceId.getInstance().getToken();
|
||||||
|
}
|
||||||
|
sendRegistrationToServer(registrationId, true);
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchUserInfo() {
|
private void fetchUserInfo() {
|
||||||
|
@ -109,14 +126,16 @@ public class MainActivity extends AppCompatActivity {
|
||||||
new Response.Listener<JSONObject>() {
|
new Response.Listener<JSONObject>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(JSONObject response) {
|
public void onResponse(JSONObject response) {
|
||||||
|
String username;
|
||||||
|
String id;
|
||||||
try {
|
try {
|
||||||
String id = response.getString("id");
|
id = response.getString("id");
|
||||||
String username = response.getString("acct");
|
username = response.getString("acct");
|
||||||
onFetchUserInfoSuccess(id, username);
|
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
//TODO: Help
|
onFetchUserInfoFailure();
|
||||||
assert (false);
|
return;
|
||||||
}
|
}
|
||||||
|
onFetchUserInfoSuccess(id, username);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Response.ErrorListener() {
|
new Response.ErrorListener() {
|
||||||
|
@ -149,9 +168,74 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private void onFetchUserInfoFailure() {
|
private void onFetchUserInfoFailure() {
|
||||||
//TODO: help
|
//TODO: help
|
||||||
assert(false);
|
Log.e(TAG, "Failed to fetch the logged-in user's info.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* @Unused: For Firebase push notifications, useless for now.
|
||||||
|
private void sendRegistrationToServer(String token, final boolean register) {
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
String domain = preferences.getString("domain", null);
|
||||||
|
final String accessToken = preferences.getString("accessToken", null);
|
||||||
|
|
||||||
|
String endpoint;
|
||||||
|
if (register) {
|
||||||
|
endpoint = getString(R.string.endpoint_devices_register);
|
||||||
|
} else {
|
||||||
|
endpoint = getString(R.string.endpoint_devices_unregister);
|
||||||
|
}
|
||||||
|
String url = "https://" + domain + endpoint;
|
||||||
|
JSONObject formData = new JSONObject();
|
||||||
|
try {
|
||||||
|
formData.put("registration_id", token);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
onSendRegistrationToServerFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, url, formData,
|
||||||
|
new Response.Listener<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(JSONObject response) {
|
||||||
|
onSendRegistrationToServerSuccess(response, register);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Response.ErrorListener() {
|
||||||
|
@Override
|
||||||
|
public void onErrorResponse(VolleyError error) {
|
||||||
|
onSendRegistrationToServerFailure();
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getHeaders() throws AuthFailureError {
|
||||||
|
Map<String, String> headers = new HashMap<>();
|
||||||
|
headers.put("Authorization", "Bearer " + accessToken);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSendRegistrationToServerSuccess(JSONObject response, boolean register) {
|
||||||
|
String registeredWord;
|
||||||
|
if (register) {
|
||||||
|
registeredWord = "registration";
|
||||||
|
} else {
|
||||||
|
registeredWord = "unregistration";
|
||||||
|
}
|
||||||
|
Log.d(TAG, String.format("Firebase %s is confirmed with the Mastodon instance. %s",
|
||||||
|
registeredWord, response.toString()));
|
||||||
|
SharedPreferences preferences = getSharedPreferences(
|
||||||
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||||
|
SharedPreferences.Editor editor = preferences.edit();
|
||||||
|
editor.putBoolean("firebaseRegistered", register);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSendRegistrationToServerFailure() {
|
||||||
|
Log.d(TAG, "Firebase registration with the Mastodon instance failed");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
private void compose() {
|
private void compose() {
|
||||||
Intent intent = new Intent(this, ComposeActivity.class);
|
Intent intent = new Intent(this, ComposeActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
@ -160,7 +244,6 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private void viewProfile() {
|
private void viewProfile() {
|
||||||
Intent intent = new Intent(this, AccountActivity.class);
|
Intent intent = new Intent(this, AccountActivity.class);
|
||||||
intent.putExtra("id", loggedInAccountId);
|
intent.putExtra("id", loggedInAccountId);
|
||||||
intent.putExtra("username", loggedInAccountUsername);
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
||||||
} else {
|
} else {
|
||||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||||
holder.setupButton(footerListener);
|
holder.setupButton(footerListener);
|
||||||
|
holder.setRetryMessage(R.string.footer_retry_notifications);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,10 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class NotificationService extends IntentService {
|
public class PullNotificationService extends IntentService {
|
||||||
private final int NOTIFY_ID = 6; // This is an arbitrary number.
|
private final int NOTIFY_ID = 6; // This is an arbitrary number.
|
||||||
|
|
||||||
public NotificationService() {
|
public PullNotificationService() {
|
||||||
super("Tusky Notification Service");
|
super("Tusky Notification Service");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v7.widget.PopupMenu;
|
import android.support.v7.widget.PopupMenu;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
@ -47,6 +48,8 @@ import java.util.Map;
|
||||||
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
* overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear
|
||||||
* up what needs to be where. */
|
* up what needs to be where. */
|
||||||
public class SFragment extends Fragment {
|
public class SFragment extends Fragment {
|
||||||
|
private static final String TAG = "SFragment"; // logging tag
|
||||||
|
|
||||||
protected String domain;
|
protected String domain;
|
||||||
protected String accessToken;
|
protected String accessToken;
|
||||||
protected String loggedInAccountId;
|
protected String loggedInAccountId;
|
||||||
|
@ -62,10 +65,6 @@ public class SFragment extends Fragment {
|
||||||
accessToken = preferences.getString("accessToken", null);
|
accessToken = preferences.getString("accessToken", null);
|
||||||
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||||
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
||||||
assert(domain != null);
|
|
||||||
assert(accessToken != null);
|
|
||||||
assert(loggedInAccountId != null);
|
|
||||||
assert(loggedInUsername != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendRequest(
|
protected void sendRequest(
|
||||||
|
@ -84,7 +83,7 @@ public class SFragment extends Fragment {
|
||||||
new Response.ErrorListener() {
|
new Response.ErrorListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onErrorResponse(VolleyError error) {
|
public void onErrorResponse(VolleyError error) {
|
||||||
System.err.println(error.getMessage());
|
Log.e(TAG, error.getMessage());
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -105,8 +104,8 @@ public class SFragment extends Fragment {
|
||||||
String inReplyToId = status.getId();
|
String inReplyToId = status.getId();
|
||||||
Status.Mention[] mentions = status.getMentions();
|
Status.Mention[] mentions = status.getMentions();
|
||||||
List<String> mentionedUsernames = new ArrayList<>();
|
List<String> mentionedUsernames = new ArrayList<>();
|
||||||
for (int i = 0; i < mentions.length; i++) {
|
for (Status.Mention mention : mentions) {
|
||||||
mentionedUsernames.add(mentions[i].getUsername());
|
mentionedUsernames.add(mention.getUsername());
|
||||||
}
|
}
|
||||||
mentionedUsernames.add(status.getUsername());
|
mentionedUsernames.add(status.getUsername());
|
||||||
mentionedUsernames.remove(loggedInUsername);
|
mentionedUsernames.remove(loggedInUsername);
|
||||||
|
|
|
@ -198,7 +198,13 @@ public class Status {
|
||||||
displayName = account.getString("username");
|
displayName = account.getString("username");
|
||||||
}
|
}
|
||||||
String username = account.getString("acct");
|
String username = account.getString("acct");
|
||||||
String avatar = account.getString("avatar");
|
String avatarUrl = account.getString("avatar");
|
||||||
|
String avatar;
|
||||||
|
if (!avatarUrl.equals("/avatars/original/missing.png")) {
|
||||||
|
avatar = avatarUrl;
|
||||||
|
} else {
|
||||||
|
avatar = "";
|
||||||
|
}
|
||||||
|
|
||||||
JSONArray mentionsArray = object.getJSONArray("mentions");
|
JSONArray mentionsArray = object.getJSONArray("mentions");
|
||||||
Mention[] mentions = null;
|
Mention[] mentions = null;
|
||||||
|
|
|
@ -68,6 +68,8 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
sinceCreated = (TextView) itemView.findViewById(R.id.status_since_created);
|
sinceCreated = (TextView) itemView.findViewById(R.id.status_since_created);
|
||||||
content = (TextView) itemView.findViewById(R.id.status_content);
|
content = (TextView) itemView.findViewById(R.id.status_content);
|
||||||
avatar = (NetworkImageView) itemView.findViewById(R.id.status_avatar);
|
avatar = (NetworkImageView) itemView.findViewById(R.id.status_avatar);
|
||||||
|
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||||
|
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||||
boostedIcon = (ImageView) itemView.findViewById(R.id.status_boosted_icon);
|
boostedIcon = (ImageView) itemView.findViewById(R.id.status_boosted_icon);
|
||||||
boostedByUsername = (TextView) itemView.findViewById(R.id.status_boosted);
|
boostedByUsername = (TextView) itemView.findViewById(R.id.status_boosted);
|
||||||
replyButton = (ImageButton) itemView.findViewById(R.id.status_reply);
|
replyButton = (ImageButton) itemView.findViewById(R.id.status_reply);
|
||||||
|
@ -147,11 +149,12 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(String url) {
|
public void setAvatar(String url) {
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Context context = avatar.getContext();
|
Context context = avatar.getContext();
|
||||||
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
|
||||||
avatar.setImageUrl(url, imageLoader);
|
avatar.setImageUrl(url, imageLoader);
|
||||||
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
|
||||||
avatar.setErrorImageResId(R.drawable.avatar_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreatedAt(@Nullable Date createdAt) {
|
public void setCreatedAt(@Nullable Date createdAt) {
|
||||||
|
|
|
@ -66,6 +66,7 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
|
||||||
} else {
|
} else {
|
||||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||||
holder.setupButton(footerListener);
|
holder.setupButton(footerListener);
|
||||||
|
holder.setRetryMessage(R.string.footer_retry_statuses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,12 +123,10 @@ public class TimelineFragment extends SFragment implements
|
||||||
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTabSelected(TabLayout.Tab tab) {
|
public void onTabSelected(TabLayout.Tab tab) {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabUnselected(TabLayout.Tab tab) {
|
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabReselected(TabLayout.Tab tab) {
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/activity_account"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<android.support.design.widget.AppBarLayout
|
<android.support.design.widget.AppBarLayout
|
||||||
|
|
6
app/src/main/res/layout/fragment_account.xml
Normal file
6
app/src/main/res/layout/fragment_account.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
48
app/src/main/res/layout/item_account.xml
Normal file
48
app/src/main/res/layout/item_account.xml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_container">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.android.volley.toolbox.NetworkImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_avatar"
|
||||||
|
android:layout_marginLeft="@dimen/account_avatar_margin"
|
||||||
|
android:layout_marginRight="@dimen/account_avatar_margin"
|
||||||
|
android:layout_marginTop="@dimen/account_avatar_margin" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toRightOf="@id/account_avatar">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_display_name"
|
||||||
|
android:textStyle="normal|bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_username" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/account_note"
|
||||||
|
android:layout_margin="@dimen/account_note_margin" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -20,7 +20,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/footer_text"
|
android:id="@+id/footer_retry_message"
|
||||||
android:padding="@dimen/footer_text_padding"/>
|
android:padding="@dimen/footer_text_padding"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
30
app/src/main/res/layout/tab_account.xml
Normal file
30
app/src/main/res/layout/tab_account.xml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textStyle="normal|bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/total"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_below="@id/title"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -7,6 +7,10 @@
|
||||||
android:icon="@drawable/ic_back"
|
android:icon="@drawable/ic_back"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item android:id="@+id/action_open_in_web"
|
||||||
|
android:title="@string/action_open_in_web"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item android:id="@+id/action_follow"
|
<item android:id="@+id/action_follow"
|
||||||
android:title="@string/action_follow"
|
android:title="@string/action_follow"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
|
@ -14,4 +14,6 @@
|
||||||
<dimen name="compose_media_preview_side">48dp</dimen>
|
<dimen name="compose_media_preview_side">48dp</dimen>
|
||||||
<dimen name="compose_mark_sensitive_margin">8dp</dimen>
|
<dimen name="compose_mark_sensitive_margin">8dp</dimen>
|
||||||
<dimen name="notification_icon_vertical_padding">4dp</dimen>
|
<dimen name="notification_icon_vertical_padding">4dp</dimen>
|
||||||
|
<dimen name="account_note_margin">8dp</dimen>
|
||||||
|
<dimen name="account_avatar_margin">8dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -37,8 +37,9 @@
|
||||||
<string name="endpoint_apps">/api/v1/apps</string>
|
<string name="endpoint_apps">/api/v1/apps</string>
|
||||||
<string name="endpoint_authorize">/oauth/authorize</string>
|
<string name="endpoint_authorize">/oauth/authorize</string>
|
||||||
<string name="endpoint_token">/oauth/token</string>
|
<string name="endpoint_token">/oauth/token</string>
|
||||||
|
<string name="endpoint_devices_register">/api/v1/devices/register</string>
|
||||||
|
<string name="endpoint_devices_unregister">/api/v1/devices/unregister</string>
|
||||||
|
|
||||||
<string name="error_fetching_timeline">Tusky failed to fetch the timeline.</string>
|
|
||||||
<string name="error_fetching_notifications">Notifications could not be fetched.</string>
|
<string name="error_fetching_notifications">Notifications could not be fetched.</string>
|
||||||
<string name="error_compose_character_limit">The status is too long!</string>
|
<string name="error_compose_character_limit">The status is too long!</string>
|
||||||
<string name="error_sending_status">The status failed to be sent.</string>
|
<string name="error_sending_status">The status failed to be sent.</string>
|
||||||
|
@ -48,6 +49,10 @@
|
||||||
<string name="error_media_upload_permission">Permission to read media is required to upload it.</string>
|
<string name="error_media_upload_permission">Permission to read media is required to upload it.</string>
|
||||||
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string>
|
<string name="error_media_upload_image_or_video">Images and videos cannot both be attached to the same status.</string>
|
||||||
<string name="error_media_upload_sending">The media could not be uploaded.</string>
|
<string name="error_media_upload_sending">The media could not be uploaded.</string>
|
||||||
|
<string name="error_following">That user wasn\'t followed.</string>
|
||||||
|
<string name="error_unfollowing">That user wasn\'t unfollowed.</string>
|
||||||
|
<string name="error_blocking">That user wasn\'t blocked.</string>
|
||||||
|
<string name="error_unblocking">That user wasn\'t unblocked.</string>
|
||||||
|
|
||||||
<string name="title_home">Home</string>
|
<string name="title_home">Home</string>
|
||||||
<string name="title_notifications">Notifications</string>
|
<string name="title_notifications">Notifications</string>
|
||||||
|
@ -63,7 +68,9 @@
|
||||||
<string name="status_sensitive_media_title">Sensitive Media</string>
|
<string name="status_sensitive_media_title">Sensitive Media</string>
|
||||||
<string name="status_sensitive_media_directions">Click to view.</string>
|
<string name="status_sensitive_media_directions">Click to view.</string>
|
||||||
|
|
||||||
<string name="footer_text">Could not load the rest of the toots.</string>
|
<string name="footer_retry_statuses">Could not load the rest of the statuses.</string>
|
||||||
|
<string name="footer_retry_notifications">Could not load the rest of the statuses.</string>
|
||||||
|
<string name="footer_retry_accounts">Could not load the rest of the accounts.</string>
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s boosted your status</string>
|
<string name="notification_reblog_format">%s boosted your status</string>
|
||||||
<string name="notification_favourite_format">%s favourited your status</string>
|
<string name="notification_favourite_format">%s favourited your status</string>
|
||||||
|
@ -83,6 +90,7 @@
|
||||||
<string name="action_cancel">Cancel</string>
|
<string name="action_cancel">Cancel</string>
|
||||||
<string name="action_back">Back</string>
|
<string name="action_back">Back</string>
|
||||||
<string name="action_profile">Profile</string>
|
<string name="action_profile">Profile</string>
|
||||||
|
<string name="action_open_in_web">Open In Web</string>
|
||||||
|
|
||||||
<string name="confirmation_send">Toot!</string>
|
<string name="confirmation_send">Toot!</string>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue