push notifications

This commit is contained in:
Thomas 2020-11-04 18:39:45 +01:00
parent 631264eb57
commit 029b8b57ae
19 changed files with 1061 additions and 272 deletions

View File

@ -130,5 +130,10 @@ dependencies {
implementation "io.github.kobakei:ratethisapp:1.2.0"
implementation 'com.github.HITGIF:TextFieldBoxes:1.4.5'
implementation 'com.github.vkay94:DoubleTapPlayerView:1.0.0'
implementation "ss.anoop:awesome-textinput-layout:1.0.3"
implementation "androidx.work:work-runtime:2.4.0"
implementation "androidx.work:work-runtime-ktx:2.4.0"
}

View File

@ -11,11 +11,29 @@
<string name="set_theme_choice" translatable="false">set_theme_choice</string>
<string name="set_fullscreen_choice" translatable="false">set_fullscreen_choice</string>
<string name="set_autoplay_next_video_choice" translatable="false">set_autoplay_next_video_choice</string>
<string name="set_store_in_history">set_store_in_history</string>
<string name="set_store_in_history" translatable="false">set_store_in_history</string>
<string name="change_profile_picture">Modifier la photo de profil</string>
<string name="account_updated">Le compte a été mis à jour !</string>
<string name="new_video">Nouvelle vidéo</string>
<string name="new_blacklist">New blacklist info</string>
<string name="new_my_video_published">Your video is published</string>
<string name="new_my_video_error">Error when publishing your video</string>
<string name="new_comment">New comment</string>
<string name="new_follow">New follow</string>
<string name="notif_new_video">Nouvelle vidéo depuis vos souscriptions</string>
<string name="notif_new_comment">Nouveau commentaire sur votre vidéo</string>
<string name="notif_blocked">Une de vos vidéos est bloquée/débloquée</string>
<string name="notif_video_published">Vidéo publiée (après transcodage / mise à jour programmée)</string>
<string name="notif_video_imported">Import de vidéo terminé</string>
<string name="notif_new_followers">Vous ou votre chaîne avez/a un·e nouvel·le abonné·e</string>
<string name="notif_video_mention">Quelqu\'un vous a mentionné dans les commentaires d\'une vidéo</string>
<string name="notif_abuse_received">Un signalement d\'abus a reçu un nouveau message</string>
<string name="notif_abuse_accepted">Un de vos rapports d\'abus a été accepté ou rejeté par les modérateurs</string>
<string name="save">Enregistrer</string>
<string name="set_autoplay_next_video">Lire automatiquement la vidéo suivante</string>
<string name="set_autoplay_next_video_description">Quand une vidéo est terminée, lire la prochaine vidéo suggérée.</string>
@ -35,6 +53,16 @@
<item>Automatique</item>
</string-array>
<string name="refresh_every">Mettre à jour toutes les :</string>
<string-array name="refresh_time">
<item>Jamais</item>
<item>15 minutes</item>
<item>30 minutes</item>
<item>1 heure</item>
<item>2 heures</item>
<item>6 heures</item>
<item>12 heures</item>
</string-array>
<plurals name="number_of_replies">
<item quantity="zero">%d réponse</item>
@ -206,7 +234,9 @@
<string name="add_public_reply">Répondre publiquement</string>
<string name="send_comment">Envoyer un commentaire</string>
<string name="all">Tout</string>
<string name="activity">Activité</string>
<string name="app">App</string>
<string name="fetch_notification_channel_name">Mise à jour des notifications</string>
<string name="peertube_video_report_success"><![CDATA[ Votre signalement <b>%1$s</b> a été accepté]]></string>
<string name="reply">Répondre</string>
<!-- end languages -->

View File

@ -7,7 +7,7 @@
<string name="set_video_quality_choice" translatable="false">set_video_quality_choice</string>
<string name="set_video_cache_choice" translatable="false">set_video_cache_choice</string>
<string name="set_autoplay_choice" translatable="false">set_autoplay_choice</string>
<string name="set_store_in_history">set_store_in_history</string>
<string name="set_store_in_history" translatable="false">set_store_in_history</string>
<string name="set_autoplay_next_video_choice" translatable="false">set_autoplay_next_video_choice</string>
<string name="set_theme_choice" translatable="false">set_theme_choice</string>
<string name="set_fullscreen_choice" translatable="false">set_fullscreen_choice</string>
@ -24,6 +24,17 @@
<string name="set_autoplay_next_video_description">When a video ends, follow up with the next suggested video.</string>
<string name="add_public_reply">Add a public reply</string>
<string name="activity">Activity</string>
<string name="app">App</string>
<string name="notif_new_video">New video from your subscriptions</string>
<string name="notif_new_comment">New comment on your video</string>
<string name="notif_blocked">One of your video is blocked/unblocked</string>
<string name="notif_video_published">Video published (after transcoding/scheduled update)</string>
<string name="notif_video_imported">Video import finished</string>
<string name="notif_new_followers">You or your channel(s) has a new follower</string>
<string name="notif_video_mention">Someone mentioned you in video comments</string>
<string name="notif_abuse_received">An abuse report received a new message</string>
<string name="notif_abuse_accepted">One of your abuse reports has been accepted or rejected by moderators</string>
<plurals name="number_of_replies">
<item quantity="one">%d reply</item>
@ -59,10 +70,28 @@
<string name="image_preview">Image preview</string>
<string name="file_to_upload">Select the file to upload</string>
<string name="new_video">New video</string>
<string name="new_blacklist">New blacklist info</string>
<string name="new_my_video_published">Your video is published</string>
<string name="new_my_video_error">Error when publishing your video</string>
<string name="new_comment">New comment</string>
<string name="new_follow">New follow</string>
<string name="channel">Channel</string>
<string name="videos">Videos</string>
<string name="channels">Channels</string>
<string name="refresh_every">Fetch every:</string>
<string-array name="refresh_time">
<item>Never</item>
<item>15 minutes</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>2 hours</item>
<item>6 hours</item>
<item>12 hours</item>
</string-array>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="cancel">Cancel</string>
@ -352,6 +381,7 @@
<string name="pickup_categories">Pick categories</string>
<string name="pickup_languages">Pick languages</string>
<string name="notification_channel_name">Update information</string>
<string name="fetch_notification_channel_name">Fetch notifications</string>
<string name="add_account">Add an account</string>
<string name="list_of_accounts">List of accounts</string>

View File

@ -150,6 +150,12 @@
<action android:name="app.fedilab.fedilabtube.uploadservice.broadcast.status" />
</intent-filter>
</receiver>
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"
android:exported="false" />
</application>
</manifest>

View File

@ -19,13 +19,34 @@ import android.content.SharedPreferences;
import androidx.multidex.MultiDex;
import androidx.multidex.MultiDexApplication;
import androidx.work.Configuration;
import androidx.work.WorkManager;
import net.gotev.uploadservice.UploadService;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.ThemeHelper;
import app.fedilab.fedilabtube.worker.WorkHelper;
public class FedilabTube extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int interval = sharedpreferences.getInt(Helper.NOTIFICATION_INTERVAL, 60);
Configuration myConfig = new Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.INFO)
.build();
WorkManager.initialize(FedilabTube.this, myConfig);
if( interval >= 15 ) {
WorkHelper.fetchNotifications(this, interval);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
@ -36,6 +57,8 @@ public class FedilabTube extends MultiDexApplication {
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int themePref = sharedpreferences.getInt(Helper.SET_THEME, Helper.DEFAULT_MODE);
ThemeHelper.switchTo(themePref);
}

View File

@ -22,20 +22,16 @@ import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.Patterns;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import java.util.Arrays;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
@ -43,6 +39,7 @@ import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.Oauth;
import app.fedilab.fedilabtube.client.entities.OauthParams;
import app.fedilab.fedilabtube.client.entities.Token;
import app.fedilab.fedilabtube.databinding.ActivityLoginBinding;
import app.fedilab.fedilabtube.helper.Helper;
import es.dmoral.toasty.Toasty;
@ -54,29 +51,26 @@ public class LoginActivity extends AppCompatActivity {
private static String client_id;
private static String client_secret;
private EditText login_uid;
private EditText login_passwd;
private Button connectionButton;
private TextInputEditText login_instance;
private ActivityLoginBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
setContentView(R.layout.activity_login);
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
TextView create_an_account_peertube = findViewById(R.id.create_an_account_peertube);
SpannableString content_create = new SpannableString(getString(R.string.join_peertube));
content_create.setSpan(new UnderlineSpan(), 0, content_create.length(), 0);
content_create.setSpan(new ForegroundColorSpan(ContextCompat.getColor(LoginActivity.this, Helper.getColorAccent())), 0, content_create.length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
create_an_account_peertube.setText(content_create);
binding.createAnAccountPeertube.setText(content_create, TextView.BufferType.SPANNABLE);
create_an_account_peertube.setOnClickListener(v -> {
binding.createAnAccountPeertube.setOnClickListener(v -> {
Intent mainActivity = new Intent(LoginActivity.this, PeertubeRegisterActivity.class);
Bundle b = new Bundle();
mainActivity.putExtras(b);
@ -84,42 +78,35 @@ public class LoginActivity extends AppCompatActivity {
});
TextInputLayout login_instance_container = findViewById(R.id.login_instance_container);
login_instance = findViewById(R.id.login_instance);
if (BuildConfig.full_instances) {
login_instance_container.setVisibility(View.VISIBLE);
binding.loginInstanceContainer.setVisibility(View.VISIBLE);
}
login_uid = findViewById(R.id.login_uid);
login_passwd = findViewById(R.id.login_passwd);
if (Helper.isTablet(LoginActivity.this)) {
ViewGroup.LayoutParams layoutParamsI = login_instance_container.getLayoutParams();
ViewGroup.LayoutParams layoutParamsI = binding.loginInstanceContainer.getLayoutParams();
layoutParamsI.width = (int) Helper.convertDpToPixel(300, LoginActivity.this);
login_instance_container.setLayoutParams(layoutParamsI);
binding.loginInstanceContainer.setLayoutParams(layoutParamsI);
TextInputLayout login_uid_container = findViewById(R.id.login_uid_container);
ViewGroup.LayoutParams layoutParamsU = login_uid_container.getLayoutParams();
ViewGroup.LayoutParams layoutParamsU = binding.loginUidContainer.getLayoutParams();
layoutParamsU.width = (int) Helper.convertDpToPixel(300, LoginActivity.this);
login_uid_container.setLayoutParams(layoutParamsU);
binding.loginUidContainer.setLayoutParams(layoutParamsU);
TextInputLayout login_passwd_container = findViewById(R.id.login_passwd_container);
ViewGroup.LayoutParams layoutParamsP = login_passwd_container.getLayoutParams();
ViewGroup.LayoutParams layoutParamsP = binding.loginPasswdContainer.getLayoutParams();
layoutParamsP.width = (int) Helper.convertDpToPixel(300, LoginActivity.this);
login_passwd_container.setLayoutParams(layoutParamsP);
binding.loginPasswdContainer.setLayoutParams(layoutParamsP);
}
connectionButton = findViewById(R.id.login_button);
if (!BuildConfig.full_instances) {
login_uid.setOnFocusChangeListener((v, hasFocus) -> {
binding.loginUid.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
if (android.util.Patterns.EMAIL_ADDRESS.matcher(login_uid.getText().toString().trim()).matches()) {
String[] emailArray = login_uid.getText().toString().split("@");
if (binding.loginUid.getText() != null && android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1 && Arrays.asList(Helper.openid).contains(emailArray[1])) {
connectionButton.callOnClick();
binding.loginButton.callOnClick();
}
}
}
@ -127,30 +114,35 @@ public class LoginActivity extends AppCompatActivity {
}
connectionButton.setOnClickListener(v -> {
binding.loginButton.setOnClickListener(v -> {
if (login_uid.getText().toString().contains("@") && !android.util.Patterns.EMAIL_ADDRESS.matcher(login_uid.getText().toString().trim()).matches()) {
if (binding.loginUid.getText() != null && binding.loginUid.getText().toString().contains("@") && !android.util.Patterns.EMAIL_ADDRESS.matcher(binding.loginUid.getText().toString().trim()).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.email_error)).show();
return;
}
connectionButton.setEnabled(false);
binding.loginButton.setEnabled(false);
String instance, host;
if (!BuildConfig.full_instances) {
String[] emailArray = login_uid.getText().toString().split("@");
String[] emailArray = binding.loginUid.getText().toString().split("@");
if (emailArray.length > 1 && !Arrays.asList(Helper.valideEmails).contains(emailArray[1])) {
Toasty.error(LoginActivity.this, getString(R.string.email_error_domain, emailArray[1])).show();
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
return;
}
host = emailArray[1];
instance = Helper.getPeertubeUrl(host);
} else {
if (login_instance == null || login_instance.getText() == null || login_instance.getText().toString().trim().length() == 0) {
if (binding.loginInstance.getText() == null || binding.loginInstance.getText().toString().trim().length() == 0) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
return;
}
instance = host = login_instance.getText().toString().trim().toLowerCase();
instance = host = binding.loginInstance.getText().toString().trim().toLowerCase();
}
if (!Patterns.WEB_URL.matcher("https://"+instance).matches()) {
Toasty.error(LoginActivity.this, getString(R.string.not_valide_instance)).show();
binding.loginButton.setEnabled(true);
return;
}
if (Arrays.asList(Helper.openid).contains(host) && !BuildConfig.full_instances) {
new Thread(() -> {
@ -158,7 +150,7 @@ public class LoginActivity extends AppCompatActivity {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, instance, null).oauthClient(null, null, null, null);
if (oauth == null) {
runOnUiThread(() -> {
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show();
});
return;
@ -179,7 +171,7 @@ public class LoginActivity extends AppCompatActivity {
} catch (Exception e) {
e.printStackTrace();
runOnUiThread(() -> {
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
Toasty.error(LoginActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
});
}
@ -190,7 +182,7 @@ public class LoginActivity extends AppCompatActivity {
Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, instance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE);
if (oauth == null) {
runOnUiThread(() -> {
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show();
});
return;
@ -209,20 +201,22 @@ public class LoginActivity extends AppCompatActivity {
oauthParams.setClient_secret(client_secret);
oauthParams.setGrant_type("password");
oauthParams.setScope("user");
oauthParams.setUsername(login_uid.getText().toString().trim());
oauthParams.setPassword(login_passwd.getText().toString());
oauthParams.setUsername(binding.loginUid.getText().toString().trim());
if( binding.loginPasswd.getText() != null) {
oauthParams.setPassword(binding.loginPasswd.getText().toString());
}
try {
Token token = new RetrofitPeertubeAPI(LoginActivity.this, instance, null).manageToken(oauthParams);
proceedLogin(token, host);
} catch (final Exception | Error e) {
oauthParams.setUsername(login_uid.getText().toString().toLowerCase().trim());
oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim());
try {
Token token = new RetrofitPeertubeAPI(LoginActivity.this, instance, null).manageToken(oauthParams);
proceedLogin(token, host);
} catch (Error error) {
Error.displayError(LoginActivity.this, error);
error.printStackTrace();
runOnUiThread(() -> connectionButton.setEnabled(true));
runOnUiThread(() -> binding.loginButton.setEnabled(true));
}
}
}).start();
@ -243,7 +237,7 @@ public class LoginActivity extends AppCompatActivity {
//Update the account with the token;
updateCredential(LoginActivity.this, token.getAccess_token(), client_id, client_secret, token.getRefresh_token(), host);
} else {
connectionButton.setEnabled(true);
binding.loginButton.setEnabled(true);
}
});
}

View File

@ -1,37 +1,4 @@
package app.fedilab.fedilabtube;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.documentfile.provider.DocumentFile;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.UserMe;
import app.fedilab.fedilabtube.client.entities.UserSettings;
import app.fedilab.fedilabtube.databinding.ActivityMyAccountSettingsBinding;
import app.fedilab.fedilabtube.helper.Helper;
import es.dmoral.toasty.Toasty;
import static app.fedilab.fedilabtube.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
@ -46,6 +13,49 @@ import static app.fedilab.fedilabtube.PeertubeUploadActivity.MY_PERMISSIONS_REQU
*
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.work.WorkManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import org.jetbrains.annotations.NotNull;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.NotificationSettings;
import app.fedilab.fedilabtube.client.entities.UserMe;
import app.fedilab.fedilabtube.client.entities.UserSettings;
import app.fedilab.fedilabtube.databinding.ActivityMyAccountSettingsBinding;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.worker.WorkHelper;
import es.dmoral.toasty.Toasty;
import static app.fedilab.fedilabtube.PeertubeUploadActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE;
import static app.fedilab.fedilabtube.worker.WorkHelper.NOTIFICATION_WORKER;
public class MyAccountActivity extends AppCompatActivity {
@ -53,6 +63,7 @@ public class MyAccountActivity extends AppCompatActivity {
private static final int PICK_IMAGE = 466;
private Uri inputData;
private String fileName;
private NotificationSettings notificationSettings;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -71,8 +82,93 @@ public class MyAccountActivity extends AppCompatActivity {
binding.displayname.setText(MainActivity.userMe.getAccount().getDisplayName());
binding.description.setText(MainActivity.userMe.getAccount().getDescription());
notificationSettings = MainActivity.userMe.getNotificationSettings();
initializeValues(notificationSettings.getAbuseStateChange(), binding.notifAbuseAcceptedApp, binding.notifAbuseAcceptedMail);
initializeValues(notificationSettings.getAbuseNewMessage(), binding.notifAbuseReceivedApp, binding.notifAbuseReceivedMail);
initializeValues(notificationSettings.getCommentMention(), binding.notifVideoMentionApp, binding.notifVideoMentionMail);
initializeValues(notificationSettings.getNewFollow(), binding.notifNewFollowersApp, binding.notifNewFollowersMail);
initializeValues(notificationSettings.getMyVideoImportFinished(), binding.notifVideoImportedApp, binding.notifVideoImportedMail);
initializeValues(notificationSettings.getMyVideoPublished(), binding.notifVideoPublishedApp, binding.notifVideoPublishedMail);
initializeValues(notificationSettings.getBlacklistOnMyVideo(), binding.notifBlockedApp, binding.notifBlockedMail);
initializeValues(notificationSettings.getNewCommentOnMyVideo(), binding.notifNewCommentApp, binding.notifNewCommentMail);
initializeValues(notificationSettings.getNewVideoFromSubscription(), binding.notifNewVideoApp, binding.notifNewVideoMail);
Helper.loadGiF(MyAccountActivity.this, MainActivity.userMe.getAccount().getAvatar()!=null?MainActivity.userMe.getAccount().getAvatar().getPath():null, binding.profilePicture);
String[] refresh_array = getResources().getStringArray(R.array.refresh_time);
ArrayAdapter<String> refreshArray = new ArrayAdapter<>(MyAccountActivity.this,
android.R.layout.simple_spinner_dropdown_item, refresh_array);
binding.refreshTime.setAdapter(refreshArray);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int interval = sharedpreferences.getInt(Helper.NOTIFICATION_INTERVAL, 60);
binding.refreshTime.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
SharedPreferences.Editor editor = sharedpreferences.edit();
int time;
switch (position) {
case 1:
time = 15;
break;
case 2:
time = 30;
break;
case 3:
time = 60;
break;
case 4:
time = 120;
break;
case 5:
time = 360;
break;
case 6:
time = 720;
break;
default:
time = 0;
}
editor.putInt(Helper.NOTIFICATION_INTERVAL, time);
editor.apply();
WorkManager.getInstance(getApplicationContext()).cancelAllWorkByTag(NOTIFICATION_WORKER);
if( time > 0 ) {
WorkHelper.fetchNotifications(getApplication(), time);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
int position = 0;
switch (interval) {
case 0:
position = 0;
break;
case 15:
position = 1;
break;
case 30:
position = 2;
break;
case 60:
position = 3;
break;
case 120:
position = 4;
break;
case 360:
position = 5;
break;
case 720:
position = 6;
break;
}
binding.refreshTime.setSelection(position, false);
binding.selectFile.setOnClickListener(v->{
if (ContextCompat.checkSelfPermission(MyAccountActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
@ -90,11 +186,24 @@ public class MyAccountActivity extends AppCompatActivity {
startActivityForResult(intent, PICK_IMAGE);
});
}
binding.save.setOnClickListener(v -> {
binding.save.setEnabled(false);
@Override
public boolean onCreateOptionsMenu(@NotNull Menu menu) {
getMenuInflater().inflate(R.menu.main_my_account, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else if (item.getItemId() == R.id.action_validate) {
item.setEnabled(false);
new Thread(() -> {
UserSettings userSettings = new UserSettings();
userSettings.setNotificationSettings(notificationSettings);
if( binding.displayname.getText() != null) {
userSettings.setDisplayName(binding.displayname.getText().toString().trim());
}
@ -113,30 +222,22 @@ public class MyAccountActivity extends AppCompatActivity {
if( avatarResponse != null && avatarResponse.getAvatar() != null ) {
MainActivity.userMe.getAccount().setAvatar(avatarResponse.getAvatar());
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
Toasty.info(MyAccountActivity.this, getString(R.string.account_updated), Toasty.LENGTH_LONG).show();
binding.save.setEnabled(true);
item.setEnabled(true);
};
mainHandler.post(myRunnable);
} catch (Exception | Error e) {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
Toasty.error(MyAccountActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
binding.save.setEnabled(true);
item.setEnabled(true);
};
mainHandler.post(myRunnable);
}
}).start();
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@ -163,4 +264,96 @@ public class MyAccountActivity extends AppCompatActivity {
}
}
private void initializeValues(int value, SwitchCompat app, SwitchCompat email) {
switch (value) {
case 1:
app.setChecked(true);
email.setChecked(false);
break;
case 2:
app.setChecked(false);
email.setChecked(true);
break;
case 3:
app.setChecked(true);
email.setChecked(true);
break;
default:
app.setChecked(false);
email.setChecked(false);
}
app.setOnCheckedChangeListener((compoundButton, checked) -> {
int id = app.getId();
if (id == R.id.notif_new_video_app) {
notificationSettings.setNewVideoFromSubscription(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_new_comment_app) {
notificationSettings.setNewCommentOnMyVideo(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_blocked_app) {
notificationSettings.setBlacklistOnMyVideo(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_video_published_app) {
notificationSettings.setMyVideoPublished(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_video_imported_app) {
notificationSettings.setMyVideoImportFinished(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_new_followers_app) {
notificationSettings.setNewFollow(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_video_mention_app) {
notificationSettings.setCommentMention(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_abuse_received_app) {
notificationSettings.setAbuseNewMessage(getNewAppCheckedValue(checked, email));
} else if (id == R.id.notif_abuse_accepted_app) {
notificationSettings.setAbuseStateChange(getNewAppCheckedValue(checked, email));
}
});
email.setOnCheckedChangeListener((compoundButtonMail, checkedMail) -> {
int id = email.getId();
if (id == R.id.notif_new_video_mail) {
notificationSettings.setNewVideoFromSubscription(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_new_comment_mail) {
notificationSettings.setNewCommentOnMyVideo(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_blocked_mail) {
notificationSettings.setBlacklistOnMyVideo(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_video_published_mail) {
notificationSettings.setMyVideoPublished(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_video_imported_mail) {
notificationSettings.setMyVideoImportFinished(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_new_followers_mail) {
notificationSettings.setNewFollow(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_video_mention_mail) {
notificationSettings.setCommentMention(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_abuse_received_mail) {
notificationSettings.setAbuseNewMessage(getNewMailCheckedValue(checkedMail, app));
} else if (id == R.id.notif_abuse_accepted_mail) {
notificationSettings.setAbuseStateChange(getNewMailCheckedValue(checkedMail, app));
}
});
}
private int getNewAppCheckedValue(boolean checked, SwitchCompat email){
int newValue;
if( checked && email.isChecked()) {
newValue = 3;
}else if( !checked && email.isChecked()) {
newValue = 2;
} else if( checked && !email.isChecked()) {
newValue = 1;
}else {
newValue = 0;
}
return newValue;
}
private int getNewMailCheckedValue(boolean checked, SwitchCompat app){
int newValue;
if( checked && app.isChecked()) {
newValue = 3;
}else if( !checked && app.isChecked()) {
newValue = 1;
} else if( checked && !app.isChecked()) {
newValue = 2;
}else {
newValue = 0;
}
return newValue;
}
}

View File

@ -25,17 +25,11 @@ import android.text.method.LinkMovementMethod;
import android.util.Patterns;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -43,6 +37,7 @@ import java.util.regex.Pattern;
import app.fedilab.fedilabtube.client.APIResponse;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.entities.AccountCreation;
import app.fedilab.fedilabtube.databinding.ActivityRegisterPeertubeBinding;
import app.fedilab.fedilabtube.helper.Helper;
import es.dmoral.toasty.Toasty;
@ -51,100 +46,89 @@ import static app.fedilab.fedilabtube.MainActivity.PICK_INSTANCE;
public class PeertubeRegisterActivity extends AppCompatActivity {
private Button signup;
private TextView error_message;
private String instance;
private TextInputEditText login_instance;
private ActivityRegisterPeertubeBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register_peertube);
binding = ActivityRegisterPeertubeBinding.inflate(getLayoutInflater());
View mainView = binding.getRoot();
setContentView(mainView);
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
signup = findViewById(R.id.signup);
TextInputLayout login_instance_container = findViewById(R.id.login_instance_container);
LinearLayout title_login_instance = findViewById(R.id.title_login_instance);
TextInputEditText username = findViewById(R.id.username);
TextInputEditText email = findViewById(R.id.email);
TextInputEditText password = findViewById(R.id.password);
TextInputEditText password_confirm = findViewById(R.id.password_confirm);
login_instance = findViewById(R.id.login_instance);
CheckBox agreement = findViewById(R.id.agreement);
error_message = findViewById(R.id.error_message);
if (BuildConfig.full_instances) {
login_instance_container.setVisibility(View.VISIBLE);
title_login_instance.setVisibility(View.VISIBLE);
binding.loginInstanceContainer.setVisibility(View.VISIBLE);
binding.titleLoginInstance.setVisibility(View.VISIBLE);
} else {
login_instance_container.setVisibility(View.GONE);
title_login_instance.setVisibility(View.GONE);
binding.loginInstanceContainer.setVisibility(View.GONE);
binding.titleLoginInstance.setVisibility(View.GONE);
}
username.setOnFocusChangeListener((view, focused) -> {
if (!focused && username.getText() != null) {
binding.username.setOnFocusChangeListener((view, focused) -> {
if (!focused && binding.username.getText() != null) {
Pattern patternUsername = Pattern.compile("^[a-z0-9._]{1,50}$");
Matcher matcherMaxId = patternUsername.matcher(username.getText().toString());
Matcher matcherMaxId = patternUsername.matcher(binding.username.getText().toString());
if (!matcherMaxId.matches()) {
username.setError(getString(R.string.username_error));
binding.username.setError(getString(R.string.username_error));
}
}
});
Button instance_help = findViewById(R.id.instance_help);
instance_help.setOnClickListener(v -> {
binding.instanceHelp.setOnClickListener(v -> {
Intent intent = new Intent(PeertubeRegisterActivity.this, InstancePickerActivity.class);
startActivityForResult(intent, PICK_INSTANCE);
});
email.setOnFocusChangeListener((view, focused) -> {
if (!focused && email.getText() != null) {
binding.email.setOnFocusChangeListener((view, focused) -> {
if (!focused && binding.email.getText() != null) {
Pattern patternUsername = Patterns.EMAIL_ADDRESS;
Matcher matcherMaxId = patternUsername.matcher(email.getText().toString());
Matcher matcherMaxId = patternUsername.matcher(binding.email.getText().toString());
if (!matcherMaxId.matches()) {
email.setError(getString(R.string.email_error));
binding.email.setError(getString(R.string.email_error));
}
}
});
password.setOnFocusChangeListener((view, focused) -> {
if (!focused && password.getText() != null) {
if (password.getText().length() < 6) {
password.setError(getString(R.string.password_length_error));
binding.password.setOnFocusChangeListener((view, focused) -> {
if (!focused && binding.password.getText() != null) {
if (binding.password.getText().length() < 6) {
binding.password.setError(getString(R.string.password_length_error));
}
}
});
password_confirm.setOnFocusChangeListener((view, focused) -> {
if (!focused && password_confirm.getText() != null && password.getText() != null) {
if (password_confirm.getText().toString().compareTo(password.getText().toString()) != 0) {
password_confirm.setError(getString(R.string.password));
binding.passwordConfirm.setOnFocusChangeListener((view, focused) -> {
if (!focused && binding.passwordConfirm.getText() != null && binding.password.getText() != null) {
if (binding.passwordConfirm.getText().toString().compareTo(binding.password.getText().toString()) != 0) {
binding.passwordConfirm.setError(getString(R.string.password));
}
}
});
setTextAgreement();
signup.setOnClickListener(view -> {
error_message.setVisibility(View.GONE);
if (username.getText() == null || email.getText() == null || password.getText() == null || password_confirm.getText() == null || username.getText().toString().trim().length() == 0 || email.getText().toString().trim().length() == 0 ||
password.getText().toString().trim().length() == 0 || password_confirm.getText().toString().trim().length() == 0 || !agreement.isChecked()) {
binding.signup.setOnClickListener(view -> {
binding.errorMessage.setVisibility(View.GONE);
if ( binding.username.getText() == null || binding.email.getText() == null || binding.password.getText() == null || binding.passwordConfirm.getText() == null || binding.username.getText().toString().trim().length() == 0 || binding.email.getText().toString().trim().length() == 0 ||
binding.password.getText().toString().trim().length() == 0 || binding.passwordConfirm.getText().toString().trim().length() == 0 || ! binding.agreement.isChecked()) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.all_field_filled)).show();
return;
}
if (!password.getText().toString().trim().equals(password_confirm.getText().toString().trim())) {
if (! binding.password.getText().toString().trim().equals( binding.passwordConfirm.getText().toString().trim())) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.password_error)).show();
return;
}
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email.getText().toString().trim()).matches()) {
if (!android.util.Patterns.EMAIL_ADDRESS.matcher( binding.email.getText().toString().trim()).matches()) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.email_error)).show();
return;
}
String[] emailArray = email.getText().toString().split("@");
String[] emailArray = binding.email.getText().toString().split("@");
if (!BuildConfig.full_instances) {
if (emailArray.length > 1 && !Arrays.asList(Helper.valideEmails).contains(emailArray[1])) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.email_error_domain, emailArray[1])).show();
@ -152,23 +136,23 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
}
}
if (password.getText().toString().trim().length() < 8) {
if ( binding.password.getText().toString().trim().length() < 8) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.password_too_short)).show();
return;
}
if (username.getText().toString().matches("[a-z0-9_]")) {
if ( binding.username.getText().toString().matches("[a-z0-9_]")) {
Toasty.error(PeertubeRegisterActivity.this, getString(R.string.username_error)).show();
return;
}
signup.setEnabled(false);
binding.signup.setEnabled(false);
if (BuildConfig.full_instances) {
if (login_instance.getText() != null) {
instance = login_instance.getText().toString();
if ( binding.loginInstance.getText() != null) {
instance = binding.loginInstance.getText().toString();
} else {
instance = "";
}
login_instance.setOnFocusChangeListener((view1, focus) -> {
binding.loginInstance.setOnFocusChangeListener((view1, focus) -> {
if (!focus) {
setTextAgreement();
}
@ -182,10 +166,10 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
}
AccountCreation accountCreation = new AccountCreation();
accountCreation.setEmail(email.getText().toString().trim());
accountCreation.setPassword(password.getText().toString().trim());
accountCreation.setPasswordConfirm(password_confirm.getText().toString().trim());
accountCreation.setUsername(username.getText().toString().trim());
accountCreation.setEmail( binding.email.getText().toString().trim());
accountCreation.setPassword( binding.password.getText().toString().trim());
accountCreation.setPasswordConfirm( binding.passwordConfirm.getText().toString().trim());
accountCreation.setUsername( binding.username.getText().toString().trim());
accountCreation.setInstance(instance);
new Thread(() -> {
@ -210,9 +194,9 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
} else {
errorMessage = getString(R.string.toast_error);
}
error_message.setText(errorMessage);
error_message.setVisibility(View.VISIBLE);
signup.setEnabled(true);
binding.errorMessage.setText(errorMessage);
binding.errorMessage.setVisibility(View.VISIBLE);
binding.signup.setEnabled(true);
return;
}
@ -260,8 +244,8 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
if (requestCode == PICK_INSTANCE && resultCode == Activity.RESULT_OK) {
if (data != null && data.getData() != null) {
String instance = String.valueOf(data.getData());
login_instance.setText(instance);
login_instance.setSelection(instance.length());
binding.loginInstance.setText(instance);
binding.loginInstance.setSelection(instance.length());
setTextAgreement();
}
}
@ -275,9 +259,9 @@ public class PeertubeRegisterActivity extends AppCompatActivity {
agreement_text.setMovementMethod(null);
agreement_text.setText(null);
if (BuildConfig.full_instances) {
if (login_instance.getText() != null) {
if ( binding.loginInstance.getText() != null) {
content_agreement = getString(R.string.agreement_check_peertube,
"<a href='https://" + login_instance.getText().toString() + "/about/instance#terms-section' >" + tos + "</a>"
"<a href='https://" + binding.loginInstance.getText().toString() + "/about/instance#terms-section' >" + tos + "</a>"
);
}
} else {

View File

@ -29,6 +29,7 @@ import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.client.data.VideoPlaylistData;
import app.fedilab.fedilabtube.client.entities.CaptionsParams;
import app.fedilab.fedilabtube.client.entities.ChannelParams;
import app.fedilab.fedilabtube.client.entities.NotificationSettings;
import app.fedilab.fedilabtube.client.entities.Oauth;
import app.fedilab.fedilabtube.client.entities.OverviewVideo;
import app.fedilab.fedilabtube.client.entities.PlaylistExist;
@ -192,6 +193,10 @@ public interface PeertubeService {
@GET("users/me/notifications")
Call<NotificationData> getNotifications(@Header("Authorization") String credentials, @Query("start") String maxId, @Query("count") String count, @Query("since_id") String sinceId);
//Update Notification settings
@PUT("users/me/notification-settings")
Call<String> updateNotifications(@Header("Authorization") String credentials, @Body NotificationSettings notificationSettings);
//Get/Post/Update/Delete video
//Get a video
@GET("videos/{id}")

View File

@ -54,6 +54,7 @@ import app.fedilab.fedilabtube.client.entities.AccountCreation;
import app.fedilab.fedilabtube.client.entities.ChannelParams;
import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.InstanceParams;
import app.fedilab.fedilabtube.client.entities.NotificationSettings;
import app.fedilab.fedilabtube.client.entities.Oauth;
import app.fedilab.fedilabtube.client.entities.OauthParams;
import app.fedilab.fedilabtube.client.entities.OverviewVideo;
@ -237,6 +238,33 @@ public class RetrofitPeertubeAPI {
return null;
}
/**
* Retrieve notifications
*
* @return APIResponse
*/
public APIResponse getNotifications() {
APIResponse apiResponse = new APIResponse();
PeertubeService peertubeService = init();
Call<NotificationData> notificationsCall = peertubeService.getNotifications("Bearer " + token, "0", "40", null);
try {
Response<NotificationData> response = notificationsCall.execute();
if (response.isSuccessful() && response.body() != null) {
apiResponse.setPeertubeNotifications(response.body().data);
} else {
setError(apiResponse, response.code(), response.errorBody());
}
} catch (IOException e) {
Error error = new Error();
error.setError(_context.getString(R.string.toast_error));
apiResponse.setError(error);
e.printStackTrace();
}
return apiResponse;
}
/**
* Retrieve notifications
*
@ -458,6 +486,9 @@ public class RetrofitPeertubeAPI {
APIResponse apiResponse = new APIResponse();
UserMe.AvatarResponse avatarResponse = null;
PeertubeService peertubeService = init();
peertubeService.updateNotifications(getToken(), userSettings.getNotificationSettings());
Call<String> updateUser = peertubeService.updateUser(getToken(),
userSettings.isVideosHistoryEnabled(),
userSettings.isAutoPlayVideo(),

View File

@ -30,6 +30,7 @@ public class UserSettings {
private String displayName;
private Uri avatarfile;
private String fileName;
private NotificationSettings notificationSettings;
public Boolean isVideosHistoryEnabled() {
return videosHistoryEnabled;
@ -122,5 +123,13 @@ public class UserSettings {
this.fileName = fileName;
}
}
public NotificationSettings getNotificationSettings() {
return notificationSettings;
}
public void setNotificationSettings(NotificationSettings notificationSettings) {
this.notificationSettings = notificationSettings;
}
}

View File

@ -42,16 +42,28 @@ import app.fedilab.fedilabtube.viewmodel.NotificationsVM;
import es.dmoral.toasty.Toasty;
@SuppressWarnings({"unused", "RedundantSuppression"})
public class DisplayNotificationsFragment extends Fragment {
//Peertube notification type
public static int NEW_VIDEO_FROM_SUBSCRIPTION = 1;
public static int UNBLACKLIST_ON_MY_VIDEO = 5;
public static int BLACKLIST_ON_MY_VIDEO = 4;
public static int MY_VIDEO_PUBLISHED = 6;
public static int MY_VIDEO_IMPORT_SUCCESS = 7;
public static int MY_VIDEO_IMPORT_ERROR = 8;
public static int MY_VIDEO_REPPORT_SUCCESS = 15;
public final static int NEW_VIDEO_FROM_SUBSCRIPTION = 1;
public final static int NEW_COMMENT_ON_MY_VIDEO = 2;
public final static int NEW_ABUSE_FOR_MODERATORS = 3;
public final static int BLACKLIST_ON_MY_VIDEO = 4;
public final static int UNBLACKLIST_ON_MY_VIDEO = 5;
public final static int MY_VIDEO_PUBLISHED = 6;
public final static int MY_VIDEO_IMPORT_SUCCESS = 7;
public final static int MY_VIDEO_IMPORT_ERROR = 8;
public final static int NEW_USER_REGISTRATION = 9;
public final static int NEW_FOLLOW = 10;
public final static int COMMENT_MENTION = 11;
public final static int VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12;
public final static int NEW_INSTANCE_FOLLOWER = 13;
public final static int AUTO_INSTANCE_FOLLOWING = 14;
public final static int MY_VIDEO_REPPORT_SUCCESS = 15;
public final static int ABUSE_NEW_MESSAGE = 16;
private boolean flag_loading;
private Context context;
private PeertubeNotificationsListAdapter peertubeNotificationsListAdapter;

View File

@ -91,6 +91,8 @@ public class Helper {
public static final int QUALITY_LOW = 2;
public static final int ADD_USER_INTENT = 5;
public static final String SET_SHARE_DETAILS = "set_share_details";
public static final String NOTIFICATION_INTERVAL = "notification_interval";
public static final String LAST_NOTIFICATION_READ = "last_notification_read";
public static final int DEFAULT_VIDEO_CACHE_MB = 100;
@SuppressWarnings({"unused", "RedundantSuppression"})
public static final String TAG = "mastodon_etalab";

View File

@ -0,0 +1,87 @@
package app.fedilab.fedilabtube.helper;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program 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.
*
* TubeLab 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 TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.RingtoneManager;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.client.data.AccountData;
import static app.fedilab.fedilabtube.worker.NotificationsWorker.FETCH_NOTIFICATION_CHANNEL_ID;
public class NotificationHelper {
/**
* Sends notification with intent
*
* @param context Context
* @param intent Intent associated to the notifcation
* @param icon Bitmap profile picture
* @param title String title of the notification
* @param message String message for the notification
*/
public static void notify_user(Context context, AccountData.Account account, Intent intent, Bitmap icon, String title, String message) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
int notificationId = (int) System.currentTimeMillis();
PendingIntent pIntent = PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_ONE_SHOT);
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, FETCH_NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_tubelab).setTicker(message)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true);
notificationBuilder.setGroup(account.getAcct())
.setContentIntent(pIntent)
.setContentText(message);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel;
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
channel = new NotificationChannel(FETCH_NOTIFICATION_CHANNEL_ID, context.getString(R.string.fetch_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
}
notificationBuilder.setContentTitle(title);
notificationBuilder.setLargeIcon(icon);
notificationManager.notify(notificationId, notificationBuilder.build());
Notification summaryNotification =
new NotificationCompat.Builder(context, FETCH_NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setContentText(context.getApplicationContext().getString(R.string.fetch_notification_channel_name))
.setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(R.drawable.ic_notification_tubelab)
.setGroup(account.getAcct())
.setGroupSummary(true)
.build();
notificationManager.notify(0, summaryNotification);
}
}

View File

@ -0,0 +1,322 @@
package app.fedilab.fedilabtube.worker;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program 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.
*
* TubeLab 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 TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.work.ForegroundInfo;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.FutureTarget;
import java.util.List;
import java.util.concurrent.ExecutionException;
import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.PeertubeActivity;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.ShowAccountActivity;
import app.fedilab.fedilabtube.client.APIResponse;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.data.AccountData;
import app.fedilab.fedilabtube.client.data.NotificationData;
import app.fedilab.fedilabtube.client.entities.Actor;
import app.fedilab.fedilabtube.client.entities.Error;
import app.fedilab.fedilabtube.client.entities.NotificationSettings;
import app.fedilab.fedilabtube.client.entities.UserMe;
import app.fedilab.fedilabtube.fragment.DisplayNotificationsFragment;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.NotificationHelper;
import app.fedilab.fedilabtube.sqlite.AccountDAO;
import app.fedilab.fedilabtube.sqlite.Sqlite;
import static android.content.Context.NOTIFICATION_SERVICE;
public class NotificationsWorker extends Worker {
private final NotificationManager notificationManager;
public static String FETCH_NOTIFICATION_CHANNEL_ID = "fetch_notification_peertube";
public static int pendingNotificationID = 1;
public NotificationsWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
notificationManager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
}
@NonNull
@Override
public Result doWork() {
Context applicationContext = getApplicationContext();
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<AccountData.Account> accounts = new AccountDAO(applicationContext, db).getAllAccount();
if( accounts == null || accounts.size() == 0) {
return Result.success();
}
setForegroundAsync(createForegroundInfo());
fetchNotification();
return Result.success();
}
@SuppressWarnings({"SwitchStatementWithoutDefaultBranch", "DuplicateBranchesInSwitch"})
private void fetchNotification() {
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
List<AccountData.Account> accounts = new AccountDAO(getApplicationContext(), db).getAllAccount();
SharedPreferences sharedpreferences = getApplicationContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
for(AccountData.Account account: accounts) {
RetrofitPeertubeAPI retrofitPeertubeAPI = new RetrofitPeertubeAPI(getApplicationContext(), account.getHost(), account.getToken());
APIResponse apiResponse = retrofitPeertubeAPI.getNotifications();
try {
UserMe userMe = retrofitPeertubeAPI.verifyCredentials();
if( userMe != null) {
List<NotificationData.Notification> notifications = apiResponse.getPeertubeNotifications();
NotificationSettings notificationSettings = userMe.getNotificationSettings();
if( apiResponse != null && apiResponse.getPeertubeNotifications() != null && apiResponse.getPeertubeNotifications().size() > 0 ) {
String last_read = sharedpreferences.getString(Helper.LAST_NOTIFICATION_READ + account.getId() + account.getHost(), null);
if( last_read != null) {
for(NotificationData.Notification notification: notifications) {
String title = "";
String message = "";
FutureTarget<Bitmap> futureBitmap = Glide.with(getApplicationContext())
.asBitmap()
.load("https://"+account.getHost()+account.getAvatar()).submit();
Bitmap icon;
try {
icon = futureBitmap.get();
} catch (Exception e) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
Intent intent = null;
if( last_read == null || notification.getId().compareTo(last_read) > 0) {
switch (notification.getType()) {
case DisplayNotificationsFragment.NEW_VIDEO_FROM_SUBSCRIPTION:
if(notificationSettings.getNewVideoFromSubscription() == 1 || notificationSettings.getNewVideoFromSubscription() == 3) {
if( notification.getVideo().getChannel().getAvatar() != null ) {
FutureTarget<Bitmap> futureBitmapChannel = Glide.with(getApplicationContext())
.asBitmap()
.load("https://"+account.getHost()+notification.getVideo().getChannel().getAvatar().getPath()).submit();
try {
icon = futureBitmapChannel.get();
} catch (Exception e) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
}else{
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
title = getApplicationContext().getString(R.string.new_video);
message = getApplicationContext().getString(R.string.peertube_video_from_subscription, notification.getVideo().getChannel().getDisplayName(), notification.getVideo().getName());
intent = new Intent(getApplicationContext(), PeertubeActivity.class);
Bundle b = new Bundle();
b.putParcelable("video", notification.getVideo());
b.putString("peertube_instance", notification.getVideo().getChannel().getHost());
b.putBoolean("isMyVideo", false);
b.putString("video_id", notification.getVideo().getId());
b.putString("video_uuid", notification.getVideo().getUuid());
intent.putExtras(b);
}
break;
case DisplayNotificationsFragment.NEW_COMMENT_ON_MY_VIDEO:
if(notificationSettings.getNewCommentOnMyVideo() == 1 || notificationSettings.getNewCommentOnMyVideo() == 3) {
if( notification.getComment().getAccount().getAvatar() != null ) {
FutureTarget<Bitmap> futureBitmapChannel = Glide.with(getApplicationContext())
.asBitmap()
.load("https://"+account.getHost()+ notification.getComment().getAccount().getAvatar().getPath()).submit();
try {
icon = futureBitmapChannel.get();
} catch (Exception e) {
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
}else{
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
title = getApplicationContext().getString(R.string.new_comment);
message = getApplicationContext().getString(R.string.peertube_comment_on_video, notification.getComment().getAccount().getDisplayName(), notification.getComment().getAccount().getUsername());
intent = new Intent(getApplicationContext(), PeertubeActivity.class);
Bundle b = new Bundle();
b.putParcelable("video", notification.getVideo());
b.putString("peertube_instance", notification.getVideo().getChannel().getHost());
b.putBoolean("isMyVideo", false);
b.putString("video_id", notification.getVideo().getId());
b.putString("video_uuid", notification.getVideo().getUuid());
intent.putExtras(b);
}
break;
case DisplayNotificationsFragment.NEW_ABUSE_FOR_MODERATORS:
break;
case DisplayNotificationsFragment.BLACKLIST_ON_MY_VIDEO:
if(notificationSettings.getBlacklistOnMyVideo() == 1 || notificationSettings.getBlacklistOnMyVideo() == 3) {
title = getApplicationContext().getString(R.string.new_blacklist);
message = getApplicationContext().getString(R.string.peertube_video_blacklist, notification.getVideo().getName());
}
break;
case DisplayNotificationsFragment.UNBLACKLIST_ON_MY_VIDEO:
if(notificationSettings.getBlacklistOnMyVideo() == 1 || notificationSettings.getBlacklistOnMyVideo() == 3) {
title = getApplicationContext().getString(R.string.new_blacklist);
message = getApplicationContext().getString(R.string.peertube_video_unblacklist, notification.getVideo().getName());
}
break;
case DisplayNotificationsFragment.MY_VIDEO_PUBLISHED:
if(notificationSettings.getMyVideoPublished() == 1 || notificationSettings.getMyVideoPublished() == 3) {
title = getApplicationContext().getString(R.string.new_my_video_published);
message = getApplicationContext().getString(R.string.peertube_video_published, notification.getVideo().getName());
}
break;
case DisplayNotificationsFragment.MY_VIDEO_IMPORT_SUCCESS:
if(notificationSettings.getMyVideoPublished() == 1 || notificationSettings.getMyVideoPublished() == 3) {
message = getApplicationContext().getString(R.string.peertube_video_import_success, notification.getVideo().getName());
title = getApplicationContext().getString(R.string.new_my_video_error);
}
break;
case DisplayNotificationsFragment.MY_VIDEO_IMPORT_ERROR:
if(notificationSettings.getMyVideoPublished() == 1 || notificationSettings.getMyVideoPublished() == 3) {
message = getApplicationContext().getString(R.string.peertube_video_import_error, notification.getVideo().getName());
title = getApplicationContext().getString(R.string.new_my_video_error);
}
break;
case DisplayNotificationsFragment.NEW_USER_REGISTRATION:
break;
case DisplayNotificationsFragment.NEW_FOLLOW:
if(notificationSettings.getNewFollow() == 1 || notificationSettings.getNewFollow() == 3) {
if( notification.getVideo().getChannel().getAvatar() != null ) {
FutureTarget<Bitmap> futureBitmapChannel = Glide.with(getApplicationContext())
.asBitmap()
.load("https://"+account.getHost()+notification.getVideo().getChannel().getAvatar().getPath()).submit();
icon = futureBitmapChannel.get();
}else{
icon = BitmapFactory.decodeResource(getApplicationContext().getResources(),
R.drawable.missing_peertube);
}
title = getApplicationContext().getString(R.string.new_video);
String type = notification.getActorFollow().getFollowing().getType();
if (type != null && type.compareTo("channel") == 0) {
message = getApplicationContext().getString(R.string.peertube_follow_channel, notification.getActorFollow().getFollower().getDisplayName(), notification.getActorFollow().getFollowing().getDisplayName());
} else {
message = getApplicationContext().getString(R.string.peertube_follow_account, notification.getActorFollow().getFollower().getDisplayName());
}
Bundle b = new Bundle();
Actor actor = notification.getActorFollow().getFollower();
AccountData.Account accountAction = new AccountData.Account();
accountAction.setAvatar(actor.getAvatar());
accountAction.setDisplayName(actor.getDisplayName());
accountAction.setHost(actor.getHost());
accountAction.setUsername(actor.getName());
intent = new Intent(getApplicationContext(), ShowAccountActivity.class);
b.putParcelable("account", accountAction);
b.putString("accountAcct", accountAction.getUsername() + "@" + accountAction.getHost());
intent.putExtras(b);
}
break;
case DisplayNotificationsFragment.COMMENT_MENTION:
break;
case DisplayNotificationsFragment.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
break;
case DisplayNotificationsFragment.NEW_INSTANCE_FOLLOWER:
break;
case DisplayNotificationsFragment.AUTO_INSTANCE_FOLLOWING:
break;
case DisplayNotificationsFragment.MY_VIDEO_REPPORT_SUCCESS:
break;
case DisplayNotificationsFragment.ABUSE_NEW_MESSAGE:
break;
}
if( message != null && icon != null && title != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
message = Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY).toString();
else
message = Html.fromHtml(message).toString();
NotificationHelper.notify_user(getApplicationContext(), account, intent, icon, title, message);
}
}else {
break;
}
}
}
editor.putString(Helper.LAST_NOTIFICATION_READ + account.getId() + account.getHost(), apiResponse.getPeertubeNotifications().get(0).getId());
editor.apply();
}
}
} catch (Error | InterruptedException | ExecutionException error) {
error.printStackTrace();
}
}
}
@NonNull
private ForegroundInfo createForegroundInfo() {
String title = getApplicationContext().getString(R.string.fetch_notification_channel_name);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(FETCH_NOTIFICATION_CHANNEL_ID,
getApplicationContext().getString(R.string.fetch_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
Intent myIntent = new Intent(getApplicationContext(), MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
getApplicationContext(),
0,
myIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), FETCH_NOTIFICATION_CHANNEL_ID)
.setContentTitle(title)
.setTicker(title)
.setProgress(100, 0, false)
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setSmallIcon(R.drawable.ic_notification_tubelab)
.setOngoing(true);
return new ForegroundInfo(pendingNotificationID, notificationBuilder.build());
}
}

View File

@ -0,0 +1,51 @@
package app.fedilab.fedilabtube.worker;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
*
* This program 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.
*
* TubeLab 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 TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.Application;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.util.concurrent.TimeUnit;
public class WorkHelper {
public static String NOTIFICATION_WORKER = "NOTIFICATION_WORKER";
public static void fetchNotifications(Application application, int interval) {
// Create Network constraint
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
WorkManager workManager = WorkManager.getInstance(application.getApplicationContext());
PeriodicWorkRequest periodicSyncDataWork =
new PeriodicWorkRequest.Builder(NotificationsWorker.class, interval, TimeUnit.MINUTES)
.setConstraints(constraints)
// setting a backoff on case the work needs to retry
.setBackoffCriteria(BackoffPolicy.LINEAR, PeriodicWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
.build();
workManager.enqueueUniquePeriodicWork(
NOTIFICATION_WORKER,
ExistingPeriodicWorkPolicy.KEEP, //Existing Periodic Work policy
periodicSyncDataWork //work request
);
}
}

View File

@ -59,56 +59,57 @@
android:paddingStart="50dp"
android:paddingEnd="50dp">
<TextView
android:id="@+id/instance_chosen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:textColor="?colorAccent"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:id="@+id/login_instance_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instance_chosen">
app:layout_constraintTop_toTopOf="parent"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/login_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title_instance_login"
android:paddingTop="2dp"
android:inputType="textWebEmailAddress"
android:singleLine="true" />
android:singleLine="true"
android:importantForAutofill="no" />
</com.google.android.material.textfield.TextInputLayout>
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:id="@+id/login_uid_container"
android:layout_width="0dp"
android:layout_marginTop="10dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_instance_container">
app:layout_constraintTop_toBottomOf="@id/login_instance_container"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/login_uid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/email_address"
android:inputType="textEmailAddress"
android:singleLine="true" />
android:singleLine="true"
android:importantForAutofill="no" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:id="@+id/login_passwd_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -116,17 +117,20 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_uid_container"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
app:passwordToggleEnabled="true"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp">
<EditText
android:id="@+id/login_passwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
android:singleLine="true"
android:importantForAutofill="no" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<Button
android:id="@+id/login_button"

View File

@ -62,20 +62,34 @@
<LinearLayout
android:visibility="gone"
android:id="@+id/title_login_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<TextView
android:layout_width="150dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:id="@+id/login_instance_container"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent">
android:labelFor="@+id/login_instance"
android:text="@string/title_instance_login" />
<EditText
android:id="@+id/login_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebEditText"
android:hint="@string/title_instance_login"
android:singleLine="true"
android:importantForAutofill="no" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<Button
android:id="@+id/instance_help"
@ -88,64 +102,44 @@
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/login_instance_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_instance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebEditText"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:labelFor="@+id/username"
android:text="@string/username" />
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLength="50" />
</com.google.android.material.textfield.TextInputLayout>
android:hint="@string/username"
android:maxLength="50"
android:importantForAutofill="no"
android:inputType="text" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp" />
<TextView
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:labelFor="@+id/email"
android:text="@string/email" />
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/email"
android:hint="@string/email"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
android:layout_height="wrap_content"
android:importantForAutofill="no"
android:inputType="textEmailAddress" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<TextView
android:layout_width="match_parent"
@ -153,24 +147,24 @@
android:text="@string/email_indicator"
android:textSize="12sp" />
<TextView
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:labelFor="@+id/password"
android:text="@string/password" />
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
android:inputType="textPassword"
android:hint="@string/password"
android:importantForAutofill="no" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<TextView
@ -179,25 +173,23 @@
android:text="@string/password_indicator"
android:textSize="12sp" />
<TextView
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:labelFor="@+id/password_confirm"
android:text="@string/password_confirm" />
<com.google.android.material.textfield.TextInputLayout
<ss.anoop.awesometextinputlayout.AwesomeTextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:borderColor="?attr/colorAccent"
app:borderWidth="1dp"
app:cornerRadius="8dp"
android:padding="16dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
<EditText
android:id="@+id/password_confirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
android:inputType="textPassword"
android:hint="@string/password_confirm"
android:importantForAutofill="no" />
</ss.anoop.awesometextinputlayout.AwesomeTextInputLayout>
<LinearLayout
android:layout_width="match_parent"

View File

@ -0,0 +1,9 @@
<?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_validate"
android:icon="@drawable/ic_check_white_24dp"
android:title="@string/validate"
app:showAsAction="always" />
</menu>