Partial account profile pages now in. Follows/Followers tabs are empty and block/follow does nothing yet.
This commit is contained in:
parent
dbb2663882
commit
60d68b0ae6
22 changed files with 791 additions and 65 deletions
|
@ -35,6 +35,7 @@
|
|||
<activity android:name=".ViewVideoActivity" />
|
||||
<activity android:name=".ViewThreadActivity" />
|
||||
<activity android:name=".ViewTagActivity" />
|
||||
<activity android:name=".AccountActivity" />
|
||||
<service
|
||||
android:name=".NotificationService"
|
||||
android:description="@string/notification_service_description"
|
||||
|
|
267
app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
Normal file
267
app/src/main/java/com/keylesspalace/tusky/AccountActivity.java
Normal file
|
@ -0,0 +1,267 @@
|
|||
/* 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.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Spanned;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.volley.AuthFailureError;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.JsonArrayRequest;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
import com.android.volley.toolbox.NetworkImageView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AccountActivity extends AppCompatActivity {
|
||||
private String domain;
|
||||
private String accessToken;
|
||||
private boolean following = false;
|
||||
private boolean blocking = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_account);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String username = intent.getStringExtra("username");
|
||||
String id = intent.getStringExtra("id");
|
||||
TextView accountName = (TextView) findViewById(R.id.account_username);
|
||||
accountName.setText(username);
|
||||
|
||||
SharedPreferences preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
domain = preferences.getString("domain", null);
|
||||
accessToken = preferences.getString("accessToken", null);
|
||||
assert(domain != null);
|
||||
assert(accessToken != null);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
NetworkImageView avatar = (NetworkImageView) findViewById(R.id.account_avatar);
|
||||
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
||||
avatar.setDefaultImageResId(R.drawable.avatar_default);
|
||||
avatar.setErrorImageResId(R.drawable.avatar_error);
|
||||
header.setDefaultImageResId(R.drawable.account_header_default);
|
||||
|
||||
obtainAccount(id);
|
||||
obtainRelationships(id);
|
||||
|
||||
// Setup the tabs and timeline pager.
|
||||
AccountPagerAdapter adapter = new AccountPagerAdapter(getSupportFragmentManager(), id);
|
||||
String[] pageTitles = {
|
||||
getString(R.string.title_statuses),
|
||||
getString(R.string.title_follows),
|
||||
getString(R.string.title_followers)
|
||||
};
|
||||
adapter.setPageTitles(pageTitles);
|
||||
ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
|
||||
viewPager.setAdapter(adapter);
|
||||
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
}
|
||||
|
||||
private void obtainAccount(String id) {
|
||||
String endpoint = String.format(getString(R.string.endpoint_accounts), id);
|
||||
String url = "https://" + domain + endpoint;
|
||||
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
onObtainAccountSuccess(response);
|
||||
} catch (JSONException e) {
|
||||
onObtainAccountFailure();
|
||||
}
|
||||
}
|
||||
},
|
||||
new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
onObtainAccountFailure();
|
||||
}
|
||||
}) {
|
||||
@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 onObtainAccountSuccess(JSONObject response) throws JSONException {
|
||||
TextView username = (TextView) findViewById(R.id.account_username);
|
||||
TextView displayName = (TextView) findViewById(R.id.account_display_name);
|
||||
TextView note = (TextView) findViewById(R.id.account_note);
|
||||
NetworkImageView avatar = (NetworkImageView) findViewById(R.id.account_avatar);
|
||||
NetworkImageView header = (NetworkImageView) findViewById(R.id.account_header);
|
||||
|
||||
String usernameFormatted = String.format(
|
||||
getString(R.string.status_username_format), response.getString("acct"));
|
||||
username.setText(usernameFormatted);
|
||||
|
||||
String displayNameString = response.getString("display_name");
|
||||
displayName.setText(displayNameString);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(displayNameString);
|
||||
}
|
||||
|
||||
String noteHtml = response.getString("note");
|
||||
Spanned noteSpanned = HtmlUtils.fromHtml(noteHtml);
|
||||
note.setText(noteSpanned);
|
||||
|
||||
ImageLoader imageLoader = VolleySingleton.getInstance(this).getImageLoader();
|
||||
avatar.setImageUrl(response.getString("avatar"), imageLoader);
|
||||
String headerUrl = response.getString("header");
|
||||
if (!headerUrl.isEmpty() && !headerUrl.equals("/headers/original/missing.png")) {
|
||||
header.setImageUrl(headerUrl, imageLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private void onObtainAccountFailure() {
|
||||
//TODO: help
|
||||
assert(false);
|
||||
}
|
||||
|
||||
private void obtainRelationships(String id) {
|
||||
String endpoint = getString(R.string.endpoint_relationships);
|
||||
String url = String.format("https://%s%s?id=%s", domain, endpoint, id);
|
||||
JsonArrayRequest request = new JsonArrayRequest(url,
|
||||
new Response.Listener<JSONArray>() {
|
||||
@Override
|
||||
public void onResponse(JSONArray response) {
|
||||
boolean following;
|
||||
boolean blocking;
|
||||
try {
|
||||
JSONObject object = response.getJSONObject(0);
|
||||
following = object.getBoolean("following");
|
||||
blocking = object.getBoolean("blocking");
|
||||
} catch (JSONException e) {
|
||||
onObtainRelationshipsFailure();
|
||||
return;
|
||||
}
|
||||
onObtainRelationshipsSuccess(following, blocking);
|
||||
}
|
||||
},
|
||||
new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
onObtainRelationshipsFailure();
|
||||
}
|
||||
}) {
|
||||
@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 onObtainRelationshipsSuccess(boolean following, boolean blocking) {
|
||||
this.following = following;
|
||||
this.blocking = blocking;
|
||||
if (!following || !blocking) {
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private void onObtainRelationshipsFailure() {
|
||||
//TODO: help
|
||||
assert(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.account_toolbar, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem follow = menu.findItem(R.id.action_follow);
|
||||
String title;
|
||||
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);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
private void follow() {
|
||||
|
||||
}
|
||||
|
||||
private void block() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_back: {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_follow: {
|
||||
follow();
|
||||
return true;
|
||||
}
|
||||
case R.id.action_block: {
|
||||
block();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/* 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.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class AccountFragment extends Fragment {
|
||||
public enum Type {
|
||||
FOLLOWS,
|
||||
FOLLOWERS,
|
||||
}
|
||||
|
||||
public static AccountFragment newInstance(Type type) {
|
||||
Bundle arguments = new Bundle();
|
||||
AccountFragment fragment = new AccountFragment();
|
||||
arguments.putString("type", type.name());
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return super.onCreateView(inflater, container, savedInstanceState);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* 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.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
|
||||
public class AccountPagerAdapter extends FragmentPagerAdapter {
|
||||
private String accountId;
|
||||
private String[] pageTitles;
|
||||
|
||||
public AccountPagerAdapter(FragmentManager manager, String accountId) {
|
||||
super(manager);
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public void setPageTitles(String[] titles) {
|
||||
pageTitles = titles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0: {
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||
}
|
||||
case 1: {
|
||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS);
|
||||
}
|
||||
case 2: {
|
||||
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return pageTitles[position];
|
||||
}
|
||||
}
|
|
@ -1,3 +1,18 @@
|
|||
/* 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 AdapterItemRemover {
|
||||
|
|
42
app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
Normal file
42
app/src/main/java/com/keylesspalace/tusky/HtmlUtils.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/* 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.os.Build;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
|
||||
public class HtmlUtils {
|
||||
private static CharSequence trimTrailingWhitespace(CharSequence s) {
|
||||
int i = s.length();
|
||||
do {
|
||||
i--;
|
||||
} while (i >= 0 && Character.isWhitespace(s.charAt(i)));
|
||||
return s.subSequence(0, i + 1);
|
||||
}
|
||||
|
||||
public static Spanned fromHtml(String html) {
|
||||
Spanned result;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
|
||||
} else {
|
||||
result = Html.fromHtml(html);
|
||||
}
|
||||
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
|
||||
* all status contents do, so it should be trimmed. */
|
||||
return (Spanned) trimTrailingWhitespace(result);
|
||||
}
|
||||
}
|
|
@ -29,16 +29,33 @@ import android.support.v7.widget.Toolbar;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.android.volley.AuthFailureError;
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private AlarmManager alarmManager;
|
||||
private PendingIntent serviceAlarmIntent;
|
||||
private boolean notificationServiceEnabled;
|
||||
private String loggedInAccountId;
|
||||
private String loggedInAccountUsername;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Fetch user info while we're doing other things.
|
||||
fetchUserInfo();
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
|
@ -75,11 +92,78 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void fetchUserInfo() {
|
||||
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 id = preferences.getString("loggedInAccountId", null);
|
||||
String username = preferences.getString("loggedInAccountUsername", null);
|
||||
if (id != null && username != null) {
|
||||
loggedInAccountId = id;
|
||||
loggedInAccountUsername = username;
|
||||
} else {
|
||||
String endpoint = getString(R.string.endpoint_verify_credentials);
|
||||
String url = "https://" + domain + endpoint;
|
||||
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
String id = response.getString("id");
|
||||
String username = response.getString("acct");
|
||||
onFetchUserInfoSuccess(id, username);
|
||||
} catch (JSONException e) {
|
||||
//TODO: Help
|
||||
assert (false);
|
||||
}
|
||||
}
|
||||
},
|
||||
new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
onFetchUserInfoFailure();
|
||||
}
|
||||
}) {
|
||||
@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 onFetchUserInfoSuccess(String id, String username) {
|
||||
loggedInAccountId = id;
|
||||
loggedInAccountUsername = username;
|
||||
SharedPreferences preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString("loggedInAccountId", loggedInAccountId);
|
||||
editor.putString("loggedInAccountUsername", loggedInAccountUsername);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void onFetchUserInfoFailure() {
|
||||
//TODO: help
|
||||
assert(false);
|
||||
}
|
||||
|
||||
private void compose() {
|
||||
Intent intent = new Intent(this, ComposeActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void viewProfile() {
|
||||
Intent intent = new Intent(this, AccountActivity.class);
|
||||
intent.putExtra("id", loggedInAccountId);
|
||||
intent.putExtra("username", loggedInAccountUsername);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void logOut() {
|
||||
if (notificationServiceEnabled) {
|
||||
alarmManager.cancel(serviceAlarmIntent);
|
||||
|
@ -108,6 +192,10 @@ public class MainActivity extends AppCompatActivity {
|
|||
compose();
|
||||
return true;
|
||||
}
|
||||
case R.id.action_profile: {
|
||||
viewProfile();
|
||||
return true;
|
||||
}
|
||||
case R.id.action_logout: {
|
||||
logOut();
|
||||
return true;
|
||||
|
|
|
@ -202,4 +202,15 @@ public class NotificationsFragment extends SFragment implements
|
|||
public void onViewTag(String tag) {
|
||||
super.viewTag(tag);
|
||||
}
|
||||
|
||||
public void onViewAccount(String id, String username) {
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
|
||||
public void onViewAccount(int position) {
|
||||
Status status = adapter.getItem(position).getStatus();
|
||||
String id = status.getAccountId();
|
||||
String username = status.getUsername();
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import com.android.volley.Response;
|
|||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -61,10 +60,12 @@ public class SFragment extends Fragment {
|
|||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
|
||||
domain = preferences.getString("domain", null);
|
||||
accessToken = preferences.getString("accessToken", null);
|
||||
loggedInAccountId = preferences.getString("loggedInAccountId", null);
|
||||
loggedInUsername = preferences.getString("loggedInAccountUsername", null);
|
||||
assert(domain != null);
|
||||
assert(accessToken != null);
|
||||
|
||||
sendUserInfoRequest();
|
||||
assert(loggedInAccountId != null);
|
||||
assert(loggedInUsername != null);
|
||||
}
|
||||
|
||||
protected void sendRequest(
|
||||
|
@ -100,22 +101,6 @@ public class SFragment extends Fragment {
|
|||
sendRequest(Request.Method.POST, endpoint, null, null);
|
||||
}
|
||||
|
||||
private void sendUserInfoRequest() {
|
||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
try {
|
||||
loggedInAccountId = response.getString("id");
|
||||
loggedInUsername = response.getString("acct");
|
||||
} catch (JSONException e) {
|
||||
//TODO: Help
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void reply(Status status) {
|
||||
String inReplyToId = status.getId();
|
||||
Status.Mention[] mentions = status.getMentions();
|
||||
|
@ -250,4 +235,11 @@ public class SFragment extends Fragment {
|
|||
intent.putExtra("hashtag", tag);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
protected void viewAccount(String id, String username) {
|
||||
Intent intent = new Intent(getContext(), AccountActivity.class);
|
||||
intent.putExtra("id", id);
|
||||
intent.putExtra("username", username);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,13 @@ import android.content.SharedPreferences;
|
|||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class SplashActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
|
|
@ -182,26 +182,6 @@ public class Status {
|
|||
return date;
|
||||
}
|
||||
|
||||
private static CharSequence trimTrailingWhitespace(CharSequence s) {
|
||||
int i = s.length();
|
||||
do {
|
||||
i--;
|
||||
} while (i >= 0 && Character.isWhitespace(s.charAt(i)));
|
||||
return s.subSequence(0, i + 1);
|
||||
}
|
||||
|
||||
private static Spanned compatFromHtml(String html) {
|
||||
Spanned result;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
|
||||
} else {
|
||||
result = Html.fromHtml(html);
|
||||
}
|
||||
/* Html.fromHtml returns trailing whitespace if the html ends in a </p> tag, which
|
||||
* all status contents do, so it should be trimmed. */
|
||||
return (Spanned) trimTrailingWhitespace(result);
|
||||
}
|
||||
|
||||
public static Status parse(JSONObject object, boolean isReblog) throws JSONException {
|
||||
String id = object.getString("id");
|
||||
String content = object.getString("content");
|
||||
|
@ -264,7 +244,7 @@ public class Status {
|
|||
status = reblog;
|
||||
status.setRebloggedByUsername(username);
|
||||
} else {
|
||||
Spanned contentPlus = compatFromHtml(content);
|
||||
Spanned contentPlus = HtmlUtils.fromHtml(content);
|
||||
status = new Status(
|
||||
id, accountId, displayName, username, contentPlus, avatar, createdAt,
|
||||
reblogged, favourited, visibility);
|
||||
|
|
|
@ -25,4 +25,6 @@ public interface StatusActionListener {
|
|||
void onViewMedia(String url, Status.MediaAttachment.Type type);
|
||||
void onViewThread(int position);
|
||||
void onViewTag(String tag);
|
||||
void onViewAccount(String id, String username);
|
||||
void onViewAccount(int position);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,8 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
username.setText(usernameText);
|
||||
}
|
||||
|
||||
public void setContent(Spanned content, final StatusActionListener listener) {
|
||||
public void setContent(Spanned content, Status.Mention[] mentions,
|
||||
final StatusActionListener listener) {
|
||||
// Redirect URLSpan's in the status content to the listener for viewing tag pages.
|
||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||
|
@ -106,17 +107,36 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
int start = builder.getSpanStart(span);
|
||||
int end = builder.getSpanEnd(span);
|
||||
int flags = builder.getSpanFlags(span);
|
||||
CharSequence tag = builder.subSequence(start, end);
|
||||
if (tag.charAt(0) == '#') {
|
||||
final String viewTag = tag.subSequence(1, tag.length()).toString();
|
||||
CharSequence text = builder.subSequence(start, end);
|
||||
if (text.charAt(0) == '#') {
|
||||
final String tag = text.subSequence(1, text.length()).toString();
|
||||
ClickableSpan newSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
listener.onViewTag(viewTag);
|
||||
listener.onViewTag(tag);
|
||||
}
|
||||
};
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(newSpan, start, end, flags);
|
||||
} else if (text.charAt(0) == '@') {
|
||||
final String accountUsername = text.subSequence(1, text.length()).toString();
|
||||
String id = null;
|
||||
for (Status.Mention mention: mentions) {
|
||||
if (mention.getUsername().equals(accountUsername)) {
|
||||
id = mention.getId();
|
||||
}
|
||||
}
|
||||
if (id != null) {
|
||||
final String accountId = id;
|
||||
ClickableSpan newSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
listener.onViewAccount(accountId, accountUsername);
|
||||
}
|
||||
};
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(newSpan, start, end, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set the contents.
|
||||
|
@ -236,7 +256,12 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
public void setupButtons(final StatusActionListener listener, final int position) {
|
||||
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewAccount(position);
|
||||
}
|
||||
});
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
@ -261,19 +286,25 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
listener.onMore(v, position);
|
||||
}
|
||||
});
|
||||
container.setOnClickListener(new View.OnClickListener() {
|
||||
/* Even though the content TextView is a child of the container, it won't respond to clicks
|
||||
* if it contains URLSpans without also setting its listener. The surrounding spans will
|
||||
* just eat the clicks instead of deferring to the parent listener, but WILL respond to a
|
||||
* listener directly on the TextView, for whatever reason. */
|
||||
View.OnClickListener viewThreadListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
});
|
||||
};
|
||||
content.setOnClickListener(viewThreadListener);
|
||||
container.setOnClickListener(viewThreadListener);
|
||||
}
|
||||
|
||||
public void setupWithStatus(Status status, StatusActionListener listener, int position) {
|
||||
setDisplayName(status.getDisplayName());
|
||||
setUsername(status.getUsername());
|
||||
setCreatedAt(status.getCreatedAt());
|
||||
setContent(status.getContent(), listener);
|
||||
setContent(status.getContent(), status.getMentions(), listener);
|
||||
setAvatar(status.getAvatar());
|
||||
setReblogged(status.getReblogged());
|
||||
setFavourited(status.getFavourited());
|
||||
|
|
|
@ -48,13 +48,14 @@ public class TimelineFragment extends SFragment implements
|
|||
MENTIONS,
|
||||
PUBLIC,
|
||||
TAG,
|
||||
USER,
|
||||
}
|
||||
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private TimelineAdapter adapter;
|
||||
private Kind kind;
|
||||
private String hashtag;
|
||||
private String hashtagOrId;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private EndlessOnScrollListener scrollListener;
|
||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||
|
@ -67,11 +68,11 @@ public class TimelineFragment extends SFragment implements
|
|||
return fragment;
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, String hashtag) {
|
||||
public static TimelineFragment newInstance(Kind kind, String hashtagOrId) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString("kind", kind.name());
|
||||
arguments.putString("hashtag", hashtag);
|
||||
arguments.putString("hashtag_or_id", hashtagOrId);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
@ -82,8 +83,8 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
Bundle arguments = getArguments();
|
||||
kind = Kind.valueOf(arguments.getString("kind"));
|
||||
if (kind == Kind.TAG) {
|
||||
hashtag = arguments.getString("hashtag");
|
||||
if (kind == Kind.TAG || kind == Kind.USER) {
|
||||
hashtagOrId = arguments.getString("hashtag_or_id");
|
||||
}
|
||||
|
||||
View rootView = inflater.inflate(R.layout.fragment_timeline, container, false);
|
||||
|
@ -118,7 +119,7 @@ public class TimelineFragment extends SFragment implements
|
|||
adapter = new TimelineAdapter(this, this);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
if (kind != Kind.TAG) {
|
||||
if (jumpToTopAllowed()) {
|
||||
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
|
@ -144,13 +145,17 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
if (kind != Kind.TAG) {
|
||||
if (jumpToTopAllowed()) {
|
||||
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||
}
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private boolean jumpToTopAllowed() {
|
||||
return kind != Kind.TAG;
|
||||
}
|
||||
|
||||
private void jumpToTop() {
|
||||
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||
scrollListener.reset();
|
||||
|
@ -173,8 +178,13 @@ public class TimelineFragment extends SFragment implements
|
|||
break;
|
||||
}
|
||||
case TAG: {
|
||||
assert(hashtag != null);
|
||||
endpoint = String.format(getString(R.string.endpoint_timelines_tag), hashtag);
|
||||
assert(hashtagOrId != null);
|
||||
endpoint = String.format(getString(R.string.endpoint_timelines_tag), hashtagOrId);
|
||||
break;
|
||||
}
|
||||
case USER: {
|
||||
assert(hashtagOrId != null);
|
||||
endpoint = String.format(getString(R.string.endpoint_statuses), hashtagOrId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -280,4 +290,15 @@ public class TimelineFragment extends SFragment implements
|
|||
public void onViewTag(String tag) {
|
||||
super.viewTag(tag);
|
||||
}
|
||||
|
||||
public void onViewAccount(String id, String username) {
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
|
||||
public void onViewAccount(int position) {
|
||||
Status status = adapter.getItem(position);
|
||||
String id = status.getAccountId();
|
||||
String username = status.getUsername();
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,4 +144,15 @@ public class ViewThreadFragment extends SFragment implements StatusActionListene
|
|||
public void onViewTag(String tag) {
|
||||
super.viewTag(tag);
|
||||
}
|
||||
|
||||
public void onViewAccount(String id, String username) {
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
|
||||
public void onViewAccount(int position) {
|
||||
Status status = adapter.getItem(position);
|
||||
String id = status.getAccountId();
|
||||
String username = status.getUsername();
|
||||
super.viewAccount(id, username);
|
||||
}
|
||||
}
|
||||
|
|
BIN
app/src/main/res/drawable/account_header_default.png
Normal file
BIN
app/src/main/res/drawable/account_header_default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
122
app/src/main/res/layout/activity_account.xml
Normal file
122
app/src/main/res/layout/activity_account.xml
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed"
|
||||
app:titleEnabled="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:background="@color/account_header_background">
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/account_header"
|
||||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.android.volley.toolbox.NetworkImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:id="@+id/account_avatar"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_toRightOf="@id/account_avatar"
|
||||
android:layout_centerVertical="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/account_display_name"
|
||||
android:textStyle="normal|bold"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<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:padding="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
android:layout_gravity="top"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/pager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<android.support.design.widget.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</android.support.design.widget.TabLayout>
|
||||
|
||||
</android.support.v4.view.ViewPager>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
|
@ -66,6 +66,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/status_since_created_left_margin" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
|
|
18
app/src/main/res/menu/account_toolbar.xml
Normal file
18
app/src/main/res/menu/account_toolbar.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?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/action_back"
|
||||
android:title="@string/action_back"
|
||||
android:icon="@drawable/ic_back"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item android:id="@+id/action_follow"
|
||||
android:title="@string/action_follow"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/action_block"
|
||||
android:title="@string/action_block"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
|
@ -9,6 +9,11 @@
|
|||
android:icon="@drawable/ic_compose"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_profile"
|
||||
android:title="@string/action_profile"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_logout"
|
||||
android:title="@string/action_logout"
|
||||
|
|
|
@ -9,4 +9,5 @@
|
|||
<color name="media_preview_unloaded_background">#DFDFDF</color>
|
||||
<color name="compose_mention">#4F5F6F</color>
|
||||
<color name="notification_content_faded">#9F9F9F</color>
|
||||
<color name="account_header_background">#FFFFFF</color>
|
||||
</resources>
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
<string name="oauth_redirect_host">oauth2redirect</string>
|
||||
<string name="preferences_file_key">com.keylesspalace.tusky.PREFERENCES</string>
|
||||
|
||||
<string name="content_uri_format_tag">content://com.keylesspalace.tusky.viewtagactivity/%s</string>
|
||||
|
||||
<string name="endpoint_status">/api/v1/statuses</string>
|
||||
<string name="endpoint_media">/api/v1/media</string>
|
||||
<string name="endpoint_timelines_home">/api/v1/timelines/home</string>
|
||||
|
@ -42,13 +40,13 @@
|
|||
|
||||
<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_compose_character_limit">The toot is too long!</string>
|
||||
<string name="error_sending_status">The toot failed to be sent.</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_media_upload_size">The file must be less than 4MB.</string>
|
||||
<string name="error_media_upload_type">That type of file is not able to be uploaded.</string>
|
||||
<string name="error_media_upload_opening">That file could not be opened.</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 toot.</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="title_home">Home</string>
|
||||
|
@ -56,6 +54,9 @@
|
|||
<string name="title_public">Public</string>
|
||||
<string name="title_thread">Thread</string>
|
||||
<string name="title_tag">#%s</string>
|
||||
<string name="title_statuses">Posts</string>
|
||||
<string name="title_follows">Follows</string>
|
||||
<string name="title_followers">Followers</string>
|
||||
|
||||
<string name="status_username_format">\@%s</string>
|
||||
<string name="status_boosted_format">%s boosted</string>
|
||||
|
@ -64,21 +65,24 @@
|
|||
|
||||
<string name="footer_text">Could not load the rest of the toots.</string>
|
||||
|
||||
<string name="notification_reblog_format">%s boosted your toot</string>
|
||||
<string name="notification_favourite_format">%s favourited your toot</string>
|
||||
<string name="notification_reblog_format">%s boosted your status</string>
|
||||
<string name="notification_favourite_format">%s favourited your status</string>
|
||||
<string name="notification_follow_format">%s followed you</string>
|
||||
|
||||
<string name="action_compose">Compose</string>
|
||||
<string name="action_login">Log In</string>
|
||||
<string name="action_logout">Log Out</string>
|
||||
<string name="action_follow">Follow</string>
|
||||
<string name="action_unfollow">Unfollow</string>
|
||||
<string name="action_block">Block</string>
|
||||
<string name="action_unblock">Unblock</string>
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="action_send">TOOT</string>
|
||||
<string name="action_retry">Retry</string>
|
||||
<string name="action_mark_sensitive">Mark Sensitive</string>
|
||||
<string name="action_cancel">Cancel</string>
|
||||
<string name="action_back">Back</string>
|
||||
<string name="action_profile">Profile</string>
|
||||
|
||||
<string name="confirmation_send">Toot!</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue